Распространенные ключевые слова синтаксиса макроса lisp: как я вообще это называю? - PullRequest
3 голосов
/ 20 августа 2011

Я просмотрел На Лиспе , Практическом Общем Лиспе и архивах SO, чтобы ответить на этот вопрос самостоятельно, но эти попытки были сорваны моей неспособностью назвать Концепция, в которой я заинтересован. Буду признателен, если кто-нибудь скажет мне канонический термин для такого рода вещей.

Этот вопрос, вероятно, лучше всего объяснить на примере. Допустим, я хочу реализовать списки в стиле Python в Common Lisp. В Python я бы написал:

[x*2 for x in range(1,10) if x > 3]

Итак, я начинаю с записи:

(listc (* 2 x) x (range 1 10) (> x 3))

и затем определение макроса, который преобразует вышесказанное в правильное понимание. Пока все хорошо.

Интерпретация этого выражения, однако, была бы непрозрачной для читателя, еще не знакомого с пониманием списка Python. То, что я действительно хотел бы написать, это следующее:

(listc (* 2 x) for x in (range 1 10) if (> x 3)) 

но мне не удалось отследить терминологию Common Lisp для этого. Кажется, что макрос loop делает именно такие вещи. Как это называется и как я могу это реализовать? Я попытался развернуть макрос в образце выражения цикла, чтобы увидеть, как оно составлено, но полученный код был непонятным. Кто-нибудь может направить меня в правильном направлении?

Заранее спасибо.

Ответы [ 3 ]

5 голосов
/ 20 августа 2011

Ну, что делает, по сути, то, что он анализирует формы, предоставленные как его тело.Например:

(defmacro listc (expr &rest forms)
  ;; 
  ;;
  ;; (listc EXP for VAR in GENERATOR [if CONDITION])
  ;;
  ;;
  (labels ((keyword-p (thing name)
             (and (symbolp thing)
                  (string= name thing))))
    (destructuring-bind (for* variable in* generator &rest tail) forms
      (unless (and (keyword-p for* "FOR") (keyword-p in* "IN"))
        (error "malformed comprehension"))
      (let ((guard (if (null tail) 't
                       (destructuring-bind (if* condition) tail
                         (unless (keyword-p if* "IF") (error "malformed comprehension"))
                         condition))))
        `(loop 
            :for ,variable :in ,generator 
            :when ,guard 
            :collecting ,expr)))))


(defun range (start end &optional (by 1))
  (loop
     :for k :upfrom start :below end :by by
     :collecting k))

Помимо хакерского "парсера", который я использовал, у этого решения есть недостаток, который нелегко решить в обычном lisp, а именно построение промежуточных списков, если вы хотите создать цепочкуВаше понимание:

(listc x for x in (listc ...) if (evenp x))

Поскольку в обычном языке нет морального эквивалента yield, трудно создать объект, для которого не требуется, чтобы промежуточные результаты были полностью материализованы.Одним из выходов из этого может быть кодирование знаний о возможных формах «генератора» в расширителе listc, так что расширитель может оптимизировать / встроить генерацию базовой последовательности, не создавая весь промежуточный список во время выполнения.

Другим способом может быть введение "ленивых списков" (ссылка указывает на схему, поскольку в обычном lisp нет эквивалентного средства - вам сначала пришлось его построить, хотя это не так.особенно трудно).

Кроме того, вы всегда можете взглянуть на код других людей, в частности, если они пытаются решить ту же или похожую проблему, например:

3 голосов
/ 21 августа 2011

Макросы являются преобразователями кода.

Существует несколько способов реализации синтаксиса макроса:

  • деструктурирование

Common Lisp предоставляет список аргументов макроса, который также предоставляет форму деструктуризации.При использовании макроса исходная форма деструктурируется в соответствии со списком аргументов.

Это ограничивает внешний вид синтаксиса макроса, но для многих применений макросов предоставляет достаточно механизмов.

См. Макро-лямбда-списки в Common Lisp.

  • синтаксический анализ

Common Lisp также предоставляет макросу доступ ко всей форме вызова макроса,Макрос затем отвечает за анализ формы.Анализатор должен быть предоставлен автором макроса или является частью реализации макроса, выполненной автором.

Примером может быть макрос INFIX:

(infix (2 + x) * (3 + sin (y)))

Реализация макроса должнареализовать анализатор инфиксов и вернуть выражение префикса:

(* (+ 2 x) (+ 3 (sin y)))
  • на основе правил

Некоторые Лиспы предоставляют синтаксические правила, которые сопоставляются сформа вызова макроса.Для соответствующего правила синтаксиса соответствующий преобразователь будет использоваться для создания новой исходной формы.Это легко реализовать в Common Lisp, но по умолчанию это не предусмотренный механизм в Common Lisp.

См. синтаксис case в Scheme.

LOOP

Для реализации LOOP -подобного синтаксиса необходимо написать синтаксический анализатор, который вызывается в макросе для синтаксического анализа исходного выражения.Обратите внимание, что синтаксический анализатор работает не с текстом, а с интернированными данными Lisp.

В прошлом (1970-е годы) он использовался в Interlisp в так называемом 'Conversational Lisp', который является синтаксисом Lisp сболее естественный язык, как поверхность.Итерация была частью этого, и затем идея итерации была перенесена в другие Лиспы (например, LOOP Маклиспа, откуда она затем была перенесена в Common Lisp).

См. PDF на ' Разговорный Лисп 'Уорреном Тейтельманном из 1970-х годов.

Синтаксис макроса LOOP немного сложен, и не легко увидеть границы между отдельными под-операторами.

См. Расширенный синтаксис для LOOP в Common Lisp.

(loop for i from 0 when (oddp i) collect i)

, такой же как:

(loop
   for i from 0
   when (oddp i)
   collect i)

Одной из проблем макроса LOOP является то, что такие символы, как FOR, FROM, WHEN и COLLECT не совпадают с пакетом "COMMON-LISP" (пространство имен).Когда я сейчас использую LOOP в исходном коде с использованием другого пакета (пространства имен), тогда это приведет к появлению новых символов в этом пространстве имен источника.По этой причине некоторые любят писать:

(loop
   :for i :from 0
   :when (oddp i)
   :collect i)

В приведенном выше коде идентификаторы для LOOP соответствующих символов находятся в пространстве имен KEYWORD.

Для облегчения как анализа, так и чтения имеетбыло предложено вернуть скобки обратно.Пример такого использования макроса может выглядеть следующим образом:

(iter (for i from 0) (when (oddp i) (collect i)))

аналогично:

(iter
  (for i from 0)
  (when (oddp i)
    (collect i)))

В вышеприведенной версии проще найти подвыражения и пройти их.

Макрос ITERATE для Common Lisp использует этот подход.

Но в обоих примерах необходимо просмотреть исходный код с помощью пользовательского кода.

2 голосов
/ 20 августа 2011

Чтобы немного дополнить ответ Дирка: Написание собственных макросов для этого вполне выполнимо и, возможно, является хорошим упражнением. Тем не менее, есть несколько возможностей для такого рода вещей (хотя бы идиоматически), таких как

Цикл очень выразительный, но имеет синтаксис, не похожий на остальную часть общего lisp. Некоторым редакторам это не нравится и они будут плохо вставлены. Однако цикл определен в стандарте. Обычно невозможно написать расширения для цикла.

Итерация еще более выразительна и имеет знакомый синтаксис. Это не требует каких-либо специальных правил для отступов, поэтому все редакторы, правильно делающие отступ для lisp, также будут корректно делать отступы. Итерация не входит в стандартную комплектацию, поэтому вам придется получить ее самостоятельно (используйте quicklisp).

Series - это фреймворк для работы над последовательностями. В большинстве случаев ряды позволяют не хранить промежуточные значения.

...