Как далеко могут идти макросы 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)))
Смотрите эту страницу, чтобы узнать, как это сделать.
Да, вы можете переопределить синтаксис, чтобы Lisp стал компилятором. Вы делаете это с помощью "Reader Macros", которые отличаются от обычных "Compiler Macros", о которых вы, вероятно, думаете.
Common Lisp имеет встроенную функцию для определения нового синтаксиса для макросов читателя и читателя для обработки этого синтаксиса. Эта обработка выполняется во время чтения (которое предшествует времени компиляции или оценки). Чтобы узнать больше об определении макросов читателя в Common Lisp, см. Hyperpec Common Lisp - вам нужно прочитать Ch. 2 "Синтаксис" и гл. 23, "Читатель". (Я полагаю, что у Scheme такая же возможность, но я не настолько знаком с ней - см. Источники Scheme для языка программирования Arc).
В качестве простого примера, давайте предположим, что вы хотите, чтобы Lisp использовал фигурные скобки, а не круглые скобки. Это требует чего-то вроде следующих определений читателя:
;; { 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 - хороший язык для написания компилятора.
Я не эксперт по Лиспу, черт возьми, я даже не программист по Лисп, но после небольшого эксперимента с языком я пришел к выводу, что через некоторое время скобки становятся "невидимыми", и вы начинаете видеть код как Вы хотите, чтобы это было. Вы начинаете уделять больше внимания синтаксическим конструкциям, которые вы создаете с помощью s-выражений и макросов, и меньше обращаете внимание на лексическую форму текста списков и скобок.
Это особенно верно, если вы пользуетесь хорошим редактором, который помогает с отступом и окраской синтаксиса (попробуйте установить в скобках цвет, очень похожий на фон).
Возможно, вам не удастся полностью заменить язык и получить синтаксис 'Ruby', но вам это не нужно. Благодаря языковой гибкости вы можете отказаться от диалекта, который, как вы хотите, следует "рублевому стилю программирования", что бы это ни значило для вас.
Я знаю, что это просто эмпирическое наблюдение, но я думаю, что у меня был один из тех моментов просветления на Лиспе, когда я это понял.
Снова и снова новички в Lisp хотят "избавиться от всех скобок". Это длится несколько недель. Ни один проект по созданию серьезного синтаксиса программирования общего назначения поверх обычного синтаксического анализатора S-выражений никогда не дойдет, потому что программисты неизменно предпочитают то, что вы в настоящее время воспринимаете как "адские скобки". Требуется немного привыкнуть, но не сильно! Как только вы привыкнете к этому, и вы действительно сможете оценить пластичность синтаксиса по умолчанию, возвращение к языкам, где есть только один способ выразить какую-либо конкретную программную конструкцию, действительно утешительно.
Тем не менее, Lisp является отличным субстратом для построения предметно-ориентированных языков. Так же хорошо, как, если не лучше, чем XML.
Удачи!
Лучшее объяснение макросов Lisp, которые я когда-либо видел, - это
https://www.youtube.com/watch?v=4NO83wZVT0A
примерно через 55 минут. Это видео с выступления Питера Сейбела, автора "Практического общего Лисп", который является лучшим учебником по Лисп.
Мотивы для макросов Lisp обычно трудно объяснить, потому что они действительно вступают в свои права в ситуациях, которые слишком длинны, чтобы представить их в простом учебном пособии. Петр приводит отличный пример; Вы можете понять это полностью, и это хорошо, правильно использует макросы Lisp.
Вы спросили: "Можете ли вы изменить функциональную природу LISP на более объектно-ориентированный синтаксис и семантику". Ответ - да. На самом деле, в Лиспе вообще не было никакого объектно-ориентированного программирования, что неудивительно, поскольку Лисп существует еще до объектно-ориентированного программирования! Но когда мы впервые узнали об ООП в 1978 году, мы смогли легко добавить его в Лисп, используя, среди прочего, макросы. В конечном итоге была разработана Common Lisp Object System (CLOS), очень мощная объектно-ориентированная система программирования, которая элегантно вписывается в Lisp. Все это можно загрузить как расширение - ничего не встроено! Все сделано с помощью макросов.
Lisp имеет совершенно другую функцию, называемую "макрос для чтения", которую можно использовать для расширения поверхностного синтаксиса языка. Используя макросы для чтения, вы можете создавать подъязыки с синтаксисом в стиле C или Ruby. Они преобразуют текст в Лисп, внутренне. Они не используются широко большинством настоящих программистов на Лиспе, в основном потому, что сложно расширить интерактивную среду разработки, чтобы понять новый синтаксис. Например, команды отступов Emacs будут сбиты с толку новым синтаксисом. Если вы энергичны, Emacs тоже расширяемый, и вы можете рассказать ему о своем новом лексическом синтаксисе.
Скобка ад? Я не вижу больше скобок в:
(function toto)
чем в:
function(toto);
И в
(if tata (toto)
(titi)
(tutu))
не более чем в:
if (tata)
toto();
else
{
titi();
tutu();
}
Я вижу меньше скобок и ';' хоть.
Обычные макросы работают со списками объектов. Чаще всего этими объектами являются другие списки (образующие деревья) и символы, но они могут быть другими объектами, такими как строки, хеш-таблицы, пользовательские объекты и т. Д. Эти структуры называются s-exps.
Поэтому, когда вы загружаете исходный файл, ваш компилятор Lisp проанализирует текст и выдаст s-exps. Макросы работают на них. Это прекрасно работает, и это замечательный способ расширить язык в духе s-exps.
Кроме того, вышеупомянутый процесс синтаксического анализа может быть расширен с помощью "макроса читателя", который позволяет вам настроить способ, которым ваш компилятор превращает текст в s-exps. Однако я предлагаю вам принять синтаксис 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/
Если вы хотите, чтобы Lisp выглядел как Ruby, используйте Ruby.
Можно использовать Ruby (и Python) очень похожим на шутку способом, что является одной из основных причин, по которой они так быстро получили признание.
@sparkes
Иногда LISP является ясным выбором языка, а именно расширений Emacs. Я уверен, что мог бы использовать Ruby для расширения Emacs, если бы захотел, но Emacs был разработан для расширения с помощью LISP, поэтому, кажется, имеет смысл использовать его в этой ситуации.
Это сложный вопрос. Поскольку lisp уже структурно настолько близок к дереву разбора, разница между большим количеством макросов и реализацией вашего собственного мини-языка в генераторе парсера не очень ясна. Но, кроме открытия и закрытия парен, вы могли бы очень легко получить что-то, что не похоже на шум.
Одно из применений макросов, которое поразило меня, - это проверка SQL-запросов к базе данных во время компиляции.
Как только вы поймете, что у вас есть полный язык под рукой во время компиляции, это открывает новые интересные перспективы. Это также означает, что вы можете выстрелить себе в ногу новыми интересными способами (например, рендеринг компиляции не воспроизводится, что может очень легко превратиться в отладочный кошмар).