Читая эссе Пола Грэма о языках программирования, можно подумать, что макросы Lisp - единственный путь. Как занятый разработчик, работающий на других платформах, я не имел права использовать макросы Lisp. Как человек, который хочет понять гул, пожалуйста, объясните, что делает эту функцию такой мощной.
Пожалуйста, также свяжите это с тем, что я бы понял из мира разработки на Python, Java, C # или C.
macros
lisp
homoiconicity
мятный
источник
источник
Ответы:
Чтобы дать краткий ответ, макросы используются для определения языковых синтаксических расширений для Common Lisp или Domain-Specific Languages (DSL). Эти языки встроены прямо в существующий код на Лиспе. Теперь DSL могут иметь синтаксис, подобный Lisp (например, интерпретатор Prolog Питера Норвига для Common Lisp) или совершенно другой (например, Infix Notation Math для Clojure).
Вот более конкретный пример:
Python имеет встроенные в язык списки. Это дает простой синтаксис для общего случая. Линия
выдает список, содержащий все четные числа от 0 до 9. В Python 1,5 дня не было такого синтаксиса; вы бы использовали что-то вроде этого:
Оба они функционально эквивалентны. Давайте вызовем нашу приостановку неверия и притворимся, что у Лиспа очень ограниченный макрос цикла, который просто выполняет итерации, и нет простого способа сделать эквивалентную работу со списком.
В Лиспе вы могли бы написать следующее. Я должен отметить, что этот надуманный пример подобран к коду Python, а не к хорошему примеру кода на Лиспе.
Прежде чем идти дальше, я должен лучше объяснить, что такое макрос. Это преобразование, выполняемое для кода за кодом. То есть фрагмент кода, читаемый интерпретатором (или компилятором), который принимает код в качестве аргумента, манипулирует и возвращает результат, который затем запускается на месте.
Конечно, это много печатать, и программисты ленивы. Таким образом, мы могли бы определить DSL для выполнения списка. Фактически, мы уже используем один макрос (макрос цикла).
Лисп определяет пару специальных синтаксических форм. Кавычка (
'
) указывает, что следующий токен является литералом. Квазицитат или backtick (`
) указывает, что следующий токен является литералом с escape-символами . Побеги указаны оператором запятой. Литерал'(1 2 3)
является эквивалентом Python[1, 2, 3]
. Вы можете назначить его другой переменной или использовать на месте. Вы можете думать`(1 2 ,x)
как эквивалент Python's,[1, 2, x]
гдеx
переменная определена ранее. Эта запись списка является частью магии, которая входит в макросы. Вторая часть - читатель Lisp, который разумно заменяет макросы на код, но это лучше всего показано ниже:Таким образом, мы можем определить макрос с именем
lcomp
(сокращение от понимания списка). Его синтаксис будет точно таким же, как у питона, который мы использовали в примере[x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
Теперь мы можем выполнить в командной строке:
Довольно аккуратно, а? Теперь это не останавливается там. У вас есть механизм или кисть, если хотите. Вы можете иметь любой синтаксис, какой только захотите. Как и
with
синтаксис Python или C # . Или .NET синтаксис LINQ. В конце концов, это то, что привлекает людей в Lisp - максимальная гибкость.источник
(loop for x from 0 below 10 when (evenp x) collect x)
, больше примеров здесь . Но на самом деле, цикл - это «просто макрос» (я фактически заново реализовал его с нуля )Вы найдете исчерпывающую дискуссию о макросе LISP здесь .
Интересное подмножество этой статьи:
источник
Общие макросы Lisp существенно расширяют «синтаксические примитивы» вашего кода.
Например, в C конструкция switch / case работает только с целочисленными типами, и если вы хотите использовать ее для чисел с плавающей запятой или строк, у вас останутся вложенные операторы if и явные сравнения. Также нет способа написать макрос на C, который сделает эту работу за вас.
Но, поскольку макрос lisp - это (по сути) программа lisp, которая принимает фрагменты кода в качестве входных данных и возвращает код для замены «вызова» макроса, вы можете расширить свой «примитивный» репертуар так, как вам хочется, обычно заканчивая тем, что заканчиваются с более читаемой программой.
Чтобы сделать то же самое в C, вы должны написать собственный препроцессор, который съест ваш исходный (не совсем C) источник и выдаст то, что может понять компилятор C. Это не неправильный способ сделать это, но это не обязательно самый простой.
источник
Макросы Lisp позволяют вам решить, когда (если вообще будет) любая часть или выражение будет оцениваться. Чтобы привести простой пример, подумайте о C:
Что это говорит: оценивать
expr1
, и, если это правда, оцениватьexpr2
и т. Д.Теперь попробуйте превратить это
&&
в функцию ... верно, вы не можете. Вызов что-то вроде:Оценим все три,
exprs
прежде чем дать ответ, независимо от того,expr1
был ли ложным!С помощью макросов LISP вы можете написать что-то вроде:
теперь у вас есть
&&
функция, которую вы можете вызвать точно так же, как функция, и она не будет оценивать любые формы, которые вы ей передадите, если они все не верны.Чтобы увидеть, как это полезно, контрастируйте:
и:
Другие вещи, которые вы можете делать с макросами, это создание новых ключевых слов и / или мини-языков (посмотрите
(loop ...)
макрос для примера), интеграция других языков в lisp, например, вы можете написать макрос, который позволит вам сказать что-то вроде:И это даже не входит в макросы Reader .
Надеюсь это поможет.
источник
(and ...
будет оценивать выражения до тех пор, пока одно из них не станет ложным, обратите внимание, что побочные эффекты, вызванные ложной оценкой, будут иметь место, только последующие выражения будут пропущены.Я не думаю, что когда-либо видел макросы Lisp, объясненные лучше, чем этот парень: http://www.defmacro.org/ramblings/lisp.html
источник
Подумайте, что вы можете сделать в C или C ++ с помощью макросов и шаблонов. Это очень полезные инструменты для управления повторяющимся кодом, но они ограничены довольно серьезными способами.
Макросы Lisp и Lisp решают эти проблемы.
Поговорите с кем-нибудь, кто освоил C ++, и спросите его, сколько времени они потратили на изучение всех шаблонных фальшивок, необходимых для метапрограммирования шаблонов. Или все сумасшедшие уловки в (превосходных) книгах, таких как Modern C ++ Design , которые все еще трудно отлаживать и (на практике) не переносимы между реальными компиляторами, даже если язык был стандартизирован в течение десятилетия. Все это исчезает, если язык, который вы используете для метапрограммирования, - это тот же язык, который вы используете для программирования!
источник
Макрос lisp принимает фрагмент программы в качестве входных данных. Этот фрагмент программы представлен структурой данных, которой можно манипулировать и трансформировать любым удобным для вас способом. В конце макрос выводит другой фрагмент программы, и этот фрагмент - то, что выполняется во время выполнения.
C # не имеет возможности макросов, однако эквивалент был бы, если бы компилятор анализировал код в дереве CodeDOM и передавал его методу, который преобразовывал это в другой CodeDOM, который затем компилировался в IL.
Это может быть использовано для реализации синтаксиса "сахара", такого как
for each
-statementusing
-clause, linqselect
-expressions и т. Д., В качестве макросов, преобразующихся в базовый код.Если бы в Java были макросы, вы могли бы реализовать синтаксис Linq в Java без необходимости изменения базового языка Sun.
Вот псевдокод того, как
using
может выглядеть макрос в стиле Lisp для реализации :источник
using
будет выглядеть так ;)Поскольку существующие ответы дают хорошие конкретные примеры, объясняющие, что макросы достигают и как, возможно, это помогло бы собрать воедино некоторые мысли о том, почему средство макросов является значительным преимуществом по сравнению с другими языками ; сначала из этих ответов, потом из другого:
- ватин
- Мэтт Кертис
- Мигель Пинг
- Питер Сейбел, в "Практическом Обыкновенном Лиспе"
источник
Я не уверен, что могу добавить некоторое понимание к всем (превосходным) сообщениям, но ...
Макросы Lisp прекрасно работают из-за синтаксической природы Lisp.
Лисп - это очень обычный язык (думайте обо всем, это список ); макросы позволяют обрабатывать данные и код одинаково (для изменения выражений lisp не требуется разбора строк или других хаков). Вы объединяете эти две функции, и у вас есть очень чистый способ изменить код.
Редактировать: я пытался сказать, что Лисп гомоичен , что означает, что структура данных для программы LISP написана в самом LISP.
Таким образом, вы получаете способ создания собственного генератора кода поверх языка, используя сам язык со всей его мощью (например, в Java вам придется взламывать байт-код, хотя некоторые фреймворки, такие как AspectJ, позволяют вам сделать это, используя другой подход, это принципиально взломать).
На практике, используя макросы, вы в конечном итоге создаете свой собственный мини-язык поверх lisp, без необходимости изучать дополнительные языки или инструменты, а также используя все возможности самого языка.
источник
S-expressions
, а не списков.Макросы Lisp представляют собой шаблон, который встречается практически в любом крупном программном проекте. В конце концов, в большой программе у вас есть определенный фрагмент кода, в котором вы понимаете, что было бы проще и менее подвержено ошибкам написать программу, которая выводит исходный код в виде текста, который затем можно просто вставить.
В Python объекты имеют два метода
__repr__
и__str__
.__str__
это просто читабельное представление человека.__repr__
возвращает представление, которое является допустимым кодом Python, то есть что-то, что может быть введено в интерпретатор как действительный Python. Таким образом, вы можете создавать небольшие фрагменты Python, которые генерируют действительный код, который можно вставить в ваш исходный код.В Лиспе весь этот процесс формализован системой макросов. Конечно, это позволяет вам создавать расширения для синтаксиса и делать всевозможные причудливые вещи, но его фактическая полезность суммируется вышеизложенным. Конечно, помогает то, что макросистема Lisp позволяет вам манипулировать этими «фрагментами» с полной мощью всего языка.
источник
Короче говоря, макросы - это преобразования кода. Они позволяют вводить много новых синтаксических конструкций. Например, рассмотрим LINQ в C #. В lisp существуют похожие языковые расширения, которые реализуются макросами (например, встроенная конструкция цикла, итерация). Макросы значительно уменьшают дублирование кода. Макросы позволяют встраивать «маленькие языки» (например, когда в c # / java для настройки используется xml, в lisp то же самое может быть достигнуто с помощью макросов). Макросы могут скрыть трудности использования библиотек.
Например, в LISP вы можете написать
и это скрывает все содержимое базы данных (транзакции, правильное закрытие соединения, выборку данных и т. д.), тогда как в C # это требует создания SqlConnections, SqlCommands, добавления SqlParameters к SqlCommands, зацикливания на SqlDataReaders, правильного их закрытия.
источник
Хотя все вышесказанное объясняет, что такое макросы и даже имеют классные примеры, я думаю, что ключевое отличие между макросом и нормальной функцией заключается в том, что LISP сначала оценивает все параметры перед вызовом функции. С макросом все наоборот, LISP передает параметры, не оцененные, в макрос. Например, если вы передадите (+ 1 2) функции, функция получит значение 3. Если вы передадите это макросу, она получит список (+ 1 2). Это может быть использовано для создания невероятно полезных вещей.
Измерьте время, необходимое для выполнения переданной функции. С функцией параметр будет оцениваться до того, как управление будет передано функции. С помощью макроса вы можете объединить ваш код между началом и остановкой вашего секундомера. Ниже приведен точно такой же код в макросе и функции, и результат очень отличается. Примечание: это надуманный пример, и реализация была выбрана так, чтобы она была идентична, чтобы лучше подчеркнуть разницу.
источник
Я получил это из обычной кулинарной книги LISP, но я думаю, что она объяснила, почему макросы LISP хороши в хорошем смысле.
«Макрос - это обычный фрагмент кода на Лиспе, который работает с другим фрагментом предполагаемого кода на Лиспе, переводя его в исполняемый Лисп (версия ближе к нему). Это может показаться немного сложным, поэтому давайте приведем простой пример. Предположим, вы хотите версия setq, которая устанавливает две переменные в одно и то же значение. Так что если вы напишите
когда
z=8
и x, и y установлены на 11. (Я не могу думать об этом, но это всего лишь пример.)Должно быть очевидно, что мы не можем определить setq2 как функцию. Если
x=50
иy=-5
, эта функция получит значения 50, -5 и 11; он не знал бы, какие переменные должны быть установлены. Что мы действительно хотим сказать, так это то, что когда вы (система Lisp) видите(setq2 v1 v2 e)
, относитесь к нему как к эквивалентному(progn (setq v1 e) (setq v2 e))
. На самом деле, это не совсем правильно, но пока подойдет. Макрос позволяет нам сделать именно это, указав программу для преобразования входного шаблона(setq2 v1 v2 e)
«в выходной шаблон(progn ...)
».Если вы подумали, что это хорошо, вы можете прочитать здесь: http://cl-cookbook.sourceforge.net/macros.html
источник
setq2
как функцию, еслиx
иy
передаются по ссылке. Однако я не знаю, возможно ли это в CL. Так что для тех, кто не знает Lisps или CL, в частности, это не очень показательный пример IMOВ Python у вас есть декораторы, у вас есть функция, которая принимает другую функцию в качестве входных данных. Вы можете делать все, что захотите: вызывать функцию, делать что-то еще, переносить вызов функции в выпуске получения ресурсов и т. Д., Но вы не можете заглянуть внутрь этой функции. Скажем, мы хотели сделать его более мощным, скажем, ваш декоратор получил код функции в виде списка, тогда вы могли не только выполнять функцию как есть, но теперь вы можете выполнять ее части, переупорядочивать строки функции и т. Д.
источник