Как далеко могут зайти макросы LISP?

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

Тем более, можно ли избавиться от ада скобок с помощью макросов? Я выучил достаточно (Emacs-) LISP, чтобы настроить Emacs с моими собственными микро-функциями, но мне очень любопытно, как далеко могут зайти макросы в настройке языка.

Ответов (14)

Решение

Это действительно хороший вопрос.

Я думаю, это тонко, но однозначно за ответ:

Макросы не застревают в s-выражениях. См. Макрос LOOP для очень сложного языка, написанного с использованием ключевых слов (символов). Итак, хотя вы можете начинать и заканчивать цикл круглыми скобками, внутри он имеет свой собственный синтаксис.

Пример:

(loop for x from 0 below 100
      when (even x)
      collect x)

При этом большинство простых макросов просто используют s-выражения. И вы бы «застряли», используя их.

Но s-выражения, как ответил Серджио, начинают казаться правильными. Синтаксис не мешает, и вы начинаете кодировать в синтаксическом дереве.

Что касается макросов для чтения, да, вы могли бы написать что-то вроде этого:

#R{
      ruby.code.goes.here
  }

Но вам нужно будет написать свой собственный синтаксический анализатор Ruby.

Вы также можете имитировать некоторые конструкции Ruby, например блоки, с помощью макросов, которые компилируются с существующими конструкциями Lisp.

#B(some lisp (code goes here))

переведет на

(lambda () (some lisp (code goes here)))

См. Эту страницу, чтобы узнать, как это сделать.

Я не эксперт по Лисп, черт возьми, я даже не программист на Лисп, но после небольшого экспериментирования с языком я пришел к выводу, что через некоторое время круглые скобки становятся `` невидимыми '', и вы начинаете видеть код как вы хотите, чтобы это было. Вы начинаете уделять больше внимания синтаксическим конструкциям, создаваемым с помощью s-exprs и макросов, и меньше - лексической форме текста списков и скобок.

Это особенно верно, если вы пользуетесь преимуществом хорошего редактора, который помогает с отступами и раскраской синтаксиса (попробуйте установить скобки на цвет, очень похожий на цвет фона).

Возможно, вам не удастся полностью заменить язык и получить синтаксис Ruby, но он вам не нужен. Благодаря гибкости языка у вас может закончиться диалект, на котором будет казаться, что вы следуете «стилю программирования Ruby», если хотите, что бы это ни значило для вас.

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

Скобки ад? Я больше не вижу скобок в:

(function toto)

чем в:

function(toto);

И в

(if tata (toto)
  (titi)
  (tutu))

не более чем в:

if (tata)
  toto();
else
{
  titi();
  tutu();
}

Я вижу меньше скобок и ";" хотя.

Да, вы можете переопределить синтаксис, чтобы Lisp стал компилятором. Вы делаете это с помощью «макросов чтения», которые отличаются от обычных «макросов компилятора», о которых вы, вероятно, думаете.

Common Lisp имеет встроенное средство для определения нового синтаксиса для чтения и макросов для обработки этого синтаксиса. Эта обработка выполняется во время чтения (которое предшествует времени компиляции или оценки). Чтобы узнать больше об определении макросов чтения в Common Lisp, см. Common Lisp Hyperspec - вы захотите прочитать Ch. 2, «Синтаксис» и гл. 23, «Читатель» . (Я считаю, что у Scheme есть те же возможности, но я не так хорошо с ними знаком - см. Исходники Scheme для языка программирования Arc ).

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

;; { and } become list delimiters, along with ( and ).
(set-syntax-from-char #\{ #\( )
(defun lcurly-brace-reader (stream inchar) ; this was way too easy to do.
  (declare (ignore inchar))
  (read-delimited-list #\} stream t))
(set-macro-character #\{ #'lcurly-brace-reader)

(set-macro-character #\} (get-macro-character #\) ))
(set-syntax-from-char #\} #\) )

;; un-lisp -- make parens meaningless
(set-syntax-from-char #\) #\] ) ; ( and ) become normal braces
(set-syntax-from-char #\( #\[ )

Вы говорите Лиспу, что {похоже на a (и что} похоже на a). Затем вы создаете функцию ( lcurly-brace-reader ), которую читатель будет вызывать всякий раз, когда увидит {, и вы используете set-macro-character эту функцию для назначения этой функции {. Затем вы говорите Лиспу, что (и) похожи на [и] (то есть, не имеет смысла синтаксис).

Другие вещи, которые вы могли бы сделать, включают, например, создание нового синтаксиса строки или использование [и] для включения нотации in-fix и обработки ее в S-выражениях.

Вы также можете выйти далеко за рамки этого, переопределив весь синтаксис с помощью ваших собственных макросимволов, которые будут запускать действия в читателе, так что небо действительно является пределом. Это лишь одна из причин, по которой Пол Грэм и другие продолжают утверждать, что Lisp - хороший язык для написания компилятора.

Снова и снова новички в Lisp хотят «избавиться от скобок». Длится несколько недель. Ни один проект по созданию серьезного синтаксиса программирования общего назначения поверх обычного синтаксического анализатора S-выражений никогда не добьется успеха, потому что программисты неизменно заканчивают тем, что предпочитают то, что вы в настоящее время воспринимаете как «ад скобок». К этому нужно немного привыкнуть, но не так уж много! Как только вы к этому привыкнете и сможете по-настоящему оценить пластичность синтаксиса по умолчанию, возвращение к языкам, где есть только один способ выразить любую конкретную программную конструкцию, действительно раздражает.

При этом Lisp - отличный субстрат для создания доменно-ориентированных языков. Так же хорошо, если не лучше, чем XML.

Удачи!

@sparkes

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

Если вы хотите, чтобы lisp выглядел как Ruby, используйте Ruby.

Можно использовать Ruby (и Python) очень шепелявым образом, что является одной из основных причин, по которым они так быстро получили признание.

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

Обычные макросы работают со списками объектов. Чаще всего эти объекты представляют собой другие списки (таким образом, образующие деревья) и символы, но они могут быть и другими объектами, такими как строки, хэш-таблицы, определяемые пользователем объекты и т. Д. Эти структуры называются s-exps .

Итак, когда вы загружаете исходный файл, ваш компилятор Lisp проанализирует текст и выдаст s-exps. С ними работают макросы. Это отлично работает, и это прекрасный способ расширить язык в духе s-exps.

Кроме того, вышеупомянутый процесс синтаксического анализа может быть расширен с помощью «макросов чтения», которые позволяют настроить способ преобразования текста компилятором в s-выражения. Однако я предлагаю вам принять синтаксис Лиспа вместо того, чтобы превращать его во что-то еще.

Вы немного сбиты с толку, когда упомянете «функциональную природу» Lisp и «объектно-ориентированный синтаксис» Ruby. Я не уверен, что такое «объектно-ориентированный синтаксис», но Lisp - это мультипарадигмальный язык, и он очень хорошо поддерживает объектно-ориентированное программирование .

Кстати, когда я говорю Lisp, я имею в виду Common Lisp .

Я предлагаю вам отбросить свои предрассудки и честно попробовать Lisp .

Да, вы можете кардинально изменить синтаксис и даже избежать «ада скобок». Для этого вам нужно будет определить новый синтаксис читателя. Посмотрите макросы читателя.

Однако я подозреваю, что для того, чтобы достичь уровня знаний Лиспа для программирования таких макросов, вам нужно погрузиться в язык до такой степени, что вы больше не будете считать скобки «адом». Т.е. к тому времени, когда вы научитесь их избегать, вы начнете принимать их как нечто хорошее.

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

http://common-lisp.net/project/cl-quasi-quote/present-class.html

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

более подробная информация доступна по адресу: http://common-lisp.net/project/cl-quasi-quote/

другой проект для расширений синтаксиса Common Lisp: http://common-lisp.net/project/cl-syntax-sugar/

То, что вы спрашиваете, чем-то похоже на вопрос, как стать экспертом в области шоколатье, чтобы вы могли удалить всю эту адскую коричневую ерунду со своего любимого шоколадного торта.

Одно из применений макросов, которое поразило меня, - это проверка запросов SQL к БД во время компиляции.

Как только вы поймете, что во время компиляции у вас есть полный язык, это откроет новые интересные перспективы. Это также означает, что вы можете выстрелить себе в ногу новыми интересными способами (например, рендеринг компиляции невоспроизводимой, что очень легко может превратиться в кошмар отладки).

The best explanation of Lisp macros I have ever seen is at

https://www.youtube.com/watch?v=4NO83wZVT0A

starting at about 55 minutes in. This is a video of a talk given by Peter Seibel, the author of "Practical Common Lisp", which is the best Lisp textbook there is.

The motivation for Lisp macros is usually hard to explain, because they really come into their own in situations that are too lengthy to present in a simple tutorial. Peter comes up with a great example; you can grasp it completely, and it makes good, proper use of Lisp macros.

You asked: "could you change the functional nature of LISP into a more object oriented syntax and semantics". The answer is yes. In fact, Lisp originally didn't have any object-oriented programming at all, not surprising since Lisp has been around since way before object-oriented programming! But when we first learned about OOP in 1978, we were able to add it to Lisp easily, using, among other things, macros. Eventually the Common Lisp Object System (CLOS) was developed, a very powerful object-oriented programming system that fits elegantly into Lisp. The whole thing can be loaded as an extension -- nothing is built-in! It's all done with macros.

В Лиспе есть совершенно другая функция, называемая «макросами чтения», которую можно использовать для расширения поверхностного синтаксиса языка. Используя макросы для чтения, вы можете создавать подъязыки с синтаксисом, подобным C или Ruby. Они внутренне преобразовывают текст в Лисп. Они не используются широко большинством настоящих программистов на Лиспе, главным образом потому, что трудно расширить интерактивную среду разработки для понимания нового синтаксиса. Например, команды отступов Emacs могут быть запутаны новым синтаксисом. Однако если вы энергичны, Emacs тоже расширяемый, и вы можете научить его своему новому лексическому синтаксису.