Подтвердить что ты не робот

Что вы можете сделать с макросами Lisp, которые вы не можете сделать с помощью первоклассных функций?

Я думаю, что понимаю макросы Lisp и их роль на этапе компиляции.

Но в Python вы можете передать функцию в другую функцию

def f(filename, g):
  try:                                
     fh = open(filename, "rb") 
     g(fh)
  finally:
     close(fh) 

Итак, мы получаем ленивую оценку здесь. Что делать с макросами, а не с функциями в качестве объектов первого класса?

4b9b3361

Ответ 1

В первую очередь Lisp также имеет первоклассные функции, поэтому вы также можете спросить: "Зачем мне нужны макросы в Lisp, если у меня уже есть функции первого класса". Ответ на этот вопрос заключается в том, что первоклассные функции не позволяют вам играть с синтаксисом.

На косметическом уровне первоклассные функции позволяют писать f(filename, some_function) или f(filename, lambda fh: fh.whatever(x)), но не f(filename, fh, fh.whatever(x)). Хотя, возможно, это хорошо, потому что в этом последнем случае гораздо менее ясно, откуда приходит fh.

Более важные функции могут содержать только действительный код. Таким образом, вы не можете написать функцию более высокого порядка reverse_function, которая принимает функцию в качестве аргумента и выполняет ее "в обратном порядке", так что reverse_function(lambda: "hello world" print) выполнит print "hello world". С помощью макроса вы можете это сделать. Конечно, этот конкретный пример довольно глупый, но эта способность чрезвычайно полезна при внедрении доменных языков.

Например, вы не могли реализовать общую конструкцию Lisp loop в python. Черт, вы даже не могли реализовать конструкцию python for ... in в python, если она не была встроена - по крайней мере, не с этим синтаксисом. Конечно, вы можете реализовать что-то вроде for(collection, function), но это намного менее симпатично.

Ответ 2

Здесь Маттиас Феллисен отвечает с 2002 года (через http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01539.html):

Я хотел бы предложить, чтобы три дисциплинированных использования макросов:

  • Подъязыки данных: я могу писать простые выражения и создавать сложные вложенные списки/массивы/таблицы с цитатой, unquote и т.д. аккуратно одеты с макросами.

  • Связывающие конструкции: я могу представить новые конструкции связывания с макросами. Это помогает мне избавиться от лямбды и сближая вещи которые принадлежат друг другу. Например, один наших обучающих пакетов содержит форму (веб-запрос ([фамилия             (string-append "Hello" first-name ", что ваш последний имя?" ])... фамилия... имя-имя...) с очевидным взаимодействием между программа и веб-потребитель. [Примечание: в ML вы можете написать  web-query (fn last-name = > ...) string_append (...), но golly что боль и ненужное рисунок.]

  • переопределение оценок: я могу представить конструкции, которые отложить/отложить оценку выражения по мере необходимости. Подумайте о циклах, новые условия, задержка/сила и т.д.
    [Примечание: в Haskell вам не нужно, чтобы один.]

Я понимаю, что Lispers используют макросы по другим причинам. Честно говоря, я считают, что отчасти это связано с компилятора, а частично к "семантическим" нарушениям в язык перевода.

Я призываю людей обратиться ко всем три вопроса, когда говорят язык X может делать то, что могут сделать макросы.

- Маттиас

Felleisen является одним из самых влиятельных исследователей в этой области. (Я не знаю, согласен ли он с этим сообщением.)

Дополнительная информация: Paul Graham On Lisp (http://www.paulgraham.com/onlisp.html; Грэм определенно не согласен с Феллезином, что это единственные полезные использование макросов) и статью Шримара Кришнамурти "Автоматы через макросы" (http://www.cs.brown.edu/~sk/Publications/Papers/Published/sk-automata-macros/).

Ответ 3

Макросы расширяются во время компиляции. Закрытия строятся во время выполнения. С помощью макросов вы можете создавать высокоэффективные компиляторы встроенных доменных языков, а с помощью функций высокого порядка вы можете применять неэффективные интерпретаторы. Эти компиляторы eDSL могут выполнять все виды статических проверок, выполнять любые дорогостоящие оптимизации, которые вам нравятся, но когда у вас есть только время выполнения, вы не можете сделать что-то дорогое.

И не стоит забывать, что макросы позволяют гораздо более гибкий синтаксис (буквально любой синтаксис) для ваших eDSL и языковых расширений.

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

Ответ 4

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

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

Очень простые преобразования кода

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

(with-open-file (stream file :direction :input)
  (do-something stream))

против.

(call-with-stream (function do-something)
                  file
                  :direction :input)

Что дает мне макрос, это немного другой синтаксис и структура кода.

Встроенный язык: расширенные конструкции итераций

Далее рассмотрим несколько другой пример:

(loop for i from 10 below 20 collect (sqr i))

против.

(collect-for 10 20 (function sqr))

Мы можем определить функцию COLLECT-FOR, которая делает то же самое для простого цикла и имеет переменные для начала, конца и функции шага.

Но LOOP предоставляет новый язык. Макрос LOOP является компилятором для этого языка. Этот компилятор может выполнить LOOP определенные оптимизации и также может проверить синтаксис во время компиляции для этого нового языка. Еще более мощный макрос цикла ITERATE. Эти мощные инструменты на уровне языка теперь могут быть написаны как библиотеки без какой-либо специальной поддержки компилятора.

Прохождение дерева кодов в макросе и внесение изменений

Следующий еще один простой пример:

(with-slots (age name) some-person
  (print name)
  (princ " "
  (princ age))

против. что-то похожее:

(flet ((age (person) (slot-value person 'age))
       (name (person) (slot-value person 'name)))
   (print (name))
   (princ " ")
   (princ (age)))

Макрос WITH-SLOTS вызывает полное перемещение вложенного исходного дерева и заменяет имя переменной вызовом (SLOT-VALUE SOME-PERSON 'name):

(progn
  (print (slot-value some-person 'name))
  (princ " "
  (princ (slot-value some-person 'age)))

В этом случае макрос может переписать отдельные части кода. Он понимает структуру языка Lisp и знает, что имя и возраст являются переменными. Он также понимает, что в некоторых ситуациях name и age могут не быть переменными и их не следует переписывать. Это приложение так называемого Code Walker, инструмента, который может перемещаться по деревьям кодов и вносить изменения в дерево кода.

Макросы могут изменять среду времени компиляции

Еще один простой пример: содержимое небольшого файла:

(defmacro oneplus (x)
  (print (list 'expanding 'oneplus 'with x))
  `(1+ ,x))

(defun example (a b)
   (+ (oneplus a) (oneplus (* a b))))

В этом примере нас не интересует макрос ONEPLUS, а сам макрос DEFMACRO.

Что интересно? В Lisp вы можете иметь файл с указанным выше содержимым и использовать компилятор файла для компиляции этого файла.

;;; Compiling file /private/tmp/test.lisp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is  on
;;; Cross referencing is on
; (TOP-LEVEL-FORM 0)
; ONEPLUS

(EXPANDING ONEPLUS SOURCE A) 
(EXPANDING ONEPLUS SOURCE (* A B)) 
; EXAMPLE
;; Processing Cross Reference Information

Итак, мы видим, что компилятор файла расширяет использование макроса ONEPLUS.

Что особенного в этом? В файле есть макроопределение, и в следующем виде мы уже используем этот новый макрос ONEPLUS. Мы никогда не загружали определение макроса в Lisp. Как-то компилятор знает и регистрирует определенный макрос ONEPLUS и затем может его использовать.

Итак, макрос DEFMACRO регистрирует вновь определенный макрос ONEPLUS в среде компиляции, поэтому компилятор знает об этом макросе - без загрузки кода. Затем макрос можно выполнить во время компиляции во время расширения макроса.

С помощью функции мы не можем этого сделать. Компилятор создает код для вызовов функций, но не запускает их. Но макрос можно запустить во время компиляции и добавить "знание" в компилятор. Это знание тогда действует во время запуска компилятора и частично забывается позже. DEFMACRO - это макрос, который выполняется во время компиляции, а затем информирует среду компиляции нового макроса.

Обратите внимание, что макрос ONEPLUS также запускается дважды, так как он используется дважды в файле. Побочным эффектом является то, что он что-то печатает. Но ONEPLUS может иметь и другие произвольные побочные эффекты. Например, он может проверить вложенный источник на базу правил и предупредить вас, если, например, закрытый код нарушает некоторые правила (подумайте о проверке стиля).

Это означает, что макрос здесь DEFMACRO может изменить язык и его среду во время компиляции файла. В других языках компилятор может предоставить специальные директивы компилятора, которые будут распознаны во время компиляции. Существует множество примеров таких макросов, которые влияют на компилятор: DEFUN, DEFCLASS, DEFMETHOD,...

Макросы могут сделать код пользователя короче

Типичным примером является макрос DEFSTRUCT для определения структур данных, подобных записи.

(defstruct person name age salary)

Выше DEFSTRUCT макрос создает код для

  • новый тип структуры person с тремя слотами
  • для чтения и записи значений
  • предикат для проверки того, имеет ли какой-либо объект класс person
  • a make-person для создания объектов структуры
  • печатное представление

Кроме того, он может:

  • записать исходный код
  • записать источник исходного кода (файл, буфер редактора, REPL,...)
  • перекрестная ссылка на исходный код

Исходный код для определения структуры - это короткая строка. Расширенный код намного длиннее.

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

Ответ 5

Вместо ответа на высокий уровень, вот конкретное предложение: прочитайте Shriram The Swine Before Perl. Я показываю, как разработать макрос, который выполняет несколько разных действий - конкретный поток управления, привязку и язык данных. (Кроме того, вы увидите, как на самом деле делать такие вещи.)

Ответ 6

Макросы наиболее полезны на языках, которые используют ту же форму для данных и кода, потому что макрос обрабатывает код как данные и создает новый код. Макрораспределение - это генерация кода точно в момент времени, который выполняется на этапе компиляции до начала оценки. Это упрощает проектирование DSL.

В Python вы не можете взять какой-то код, передать его функции, которая генерирует новый код, чтобы выполнить новый код. Чтобы достичь чего-то вроде макросов в Python, вы должны сгенерировать AST из своего кода на Python, изменить AST и оценить измененный AST.

Это означает, что вы не можете написать инструкцию if в Python. Вы можете использовать только существующую, но вы не можете ее изменить или написать свои собственные заявления. Но макросы Lisp позволяют вам писать свои собственные заявления. Например, вы можете написать оператор fi, который ведет себя как if, но в качестве первого аргумента принимает часть else, а затем - вторую.

В следующей статье описывается разница между макросами и процедурами более подробно: ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_130.html

Ответ 7

В примере, отличном от lisp, например, эликсир, оператор потока управления if на самом деле является макросом. if реализуется как функция. Но для более чистого более запоминающегося синтаксиса он также был реализован как макрос.

if true do 1+2 end
if true, do: ( 1+2 )
if(true, do: 1+2)
if(true, [do: (1+2)])
if(true, [{:do, 1+2}])

Все вышеперечисленное эквивалентно. Но первая строка - это реализация макросов if, которая, предположительно, расширяется до функции if ниже.

Сделав, если функция и доступная в виде макроса дает вам эту классную способность, поместите if управляющие потоки внутри параметра другой функции, сохраняя при этом знакомство с другими языками.

is_number(if true do 1+2 end)
is_number(if true, do: (1+2))

Итак, я думаю, что макросы позволяют вам лучше управлять синтаксисом, тем самым позволяя создавать DSL, которые не могут выполнять стандартные функции.