Let Over Lambda - 50 Years of Lisp / Let Over Lambda - 50 лет Lisp-у: Глава 4 - Считывающие Макросы
| Английский оригинал | Перевод на русский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
4.1 Время-Работы и Время-Чтения Charlz_Klug 24.08.13 в 04:46 |
|
|
Синтаксический сахар вызывает рак точек с запятой Charlz_Klug 24.08.13 в 05:04 |
||
|
Не один только Лисп даёт прямой доступ к коду, который может разбираться на структуры из cons ячеек, но, кроме этого Лисп предоставляет доступ к символам, составляющим ваши программы, ещё до того, как программы будут представлять из себя структуру. Обычный макрос работает с программой в форме дерева. Кроме обычного макроса существует специальный тип макросов, называемых считывающими макросами (read macro), оперирующими с сырыми символами, из которых состоит ваша программа. Charlz_Klug 24.08.13 в 06:01 |
||
|
Если нам понадобиться определить не-Лисповский синтаксис в Лиспе, то в этом случае не имеет смысла использовать Лисповский считыватель - он предназначен только для чтения Лиспа. Считывающий макрос - это устройство, используемое для обработки не-Лисп синтаксиса, после считывающего макроса в дело вступает Лисп считыватель. Причина, по которой Лисп считыватель является более мощным чем считыватели в других языках заключается в том, что Лисп даёт нам возможность контролировать (hooks) каждый аспект поведения считывателя. В частности, Лисп позволяет вам расширять считыватель, таким образом, что не-Лисп объекты будут прочитываться и преобразовываться в Лисп объекты. Также, как вы строите ваши приложения поверх Лиспа, расширяя его с помощью макросов и функций, также и Лисп приложения могут, а чаще всего и просачиваются в это измерение расширяемости. Когда это происходит, то становится возможным чтение любого символа на основе синтаксиса с помощью Лисп считывателя, это означает что вы добавили этот синтаксис в Лисп. Charlz_Klug 24.08.13 в 06:29 |
||
|
В то время, когда трансформация кода, выполняемая обычными макросами используется для вставки Лисп кода в новый Лисп код, считывающие макросы могут быть написаны для вставки не-Лисп кода в Лисп код. Подобно обычным макросам, считывающие макросы реализуются с помощью функций, благодаря этому мы имеем доступ ко всей мощи Лисп окружения. Подобно макросам, увеличивающим продуктивность через создание более удобного предметно ориентированного языка, считывающие макросы увеличивают продуктивность позволяя сокращать выражения в точку где они больше не являются выражениями. Charlz_Klug 26.08.13 в 04:19 |
||
|
Если всё что нам нужно для того, чтобы разобрать эти не-Лисповские предметно-ориентированные языки - это написать короткий считывающий макрос, то может быть эти не-Лисп языки на самом деле являются умно замаскированным Лиспом. Если XML может прямо считываться Лисп считывателем [XML-AS-READ-MACRO], то может быть XML - это просто видоизменённый Лисп. Подобным образом считывающие макросы могут использоваться для чтения регулярных выражений и SQL запросов прямо в Лисп, то может быть эти языки на самом деле являются Лиспом. Это нечёткое различие между кодом и данными, Лиспом и не-Лиспом, являются источником многих интересных философских проблем, возникающих перед Лисп программистами с самого начала зарождения Лиспа. Charlz_Klug 26.08.13 в 04:53 |
||
|
Базовый считывающий макрос, встроенный в Common Lisp - это #. вычисляющий макрос в момент чтения. Этот считывающий макрос позволяет вам встраивать объекты в читаемые, не сериализируемые формы, но создаваемые с помощью Лисп кода. Один забавный пример - это создание формы, содержащей изменяющиеся значения при каждом вызове. Charlz_Klug 26.08.13 в 05:11 |
||
|
* ’(football-game Charlz_Klug 26.08.13 в 05:12 |
||
|
Несмотря на то, что это то-же самое выражение, эта форма считывается по разному раз за разом: Charlz_Klug 26.08.13 в 05:14 |
||
|
* ’(football-game Charlz_Klug 26.08.13 в 09:54 |
||
|
Заметьте, что две формы окружённые #. вычисляются в момент чтения, а не тогда, когда вычисляется форма. Полный список был сформирован после того, как они были вычислены, а предыдущая и последующая эквивалентность (определённая equal'ом) может быть обнаружена через повторное вычисление последней прочитанной формы и сравнения её с предыдущими результатами, с помощью вспомогательных переменных *, + в REPL 1: Charlz_Klug 26.08.13 в 10:08 |
||
|
1 Переменная * содержит значение, являющееся результатом предыдущей формы, а переменная + содержит значение последней формы. Charlz_Klug 26.08.13 в 10:12 |
||
|
* (equal * (eval +)) Charlz_Klug 26.08.13 в 10:12 |
||
|
Следует обратить внимание на то, что эти формы вычисляются в момент чтения, этим они отличаются от использования обратной кавычки, более подробно мы рассмотрим обратную кавычку в следующем разделе. Мы можем вычислить подобную форму с помощью использования обратных кавычек: Charlz_Klug 26.08.13 в 10:16 |
||
|
* ‘(football-game Charlz_Klug 26.08.13 в 10:18 |
||
|
но в этом случае будет происходить вычисление в различные результаты, поскольку обратная кавычка производит считывание в виде кода для вычисления: Charlz_Klug 26.08.13 в 10:41 |
||
|
* (equal * (eval +)) Charlz_Klug 26.08.13 в 10:48 * (equal * (eval +)) Charlz_Klug 26.08.13 в 10:49 * (equal * (eval +)) Charlz_Klug 26.08.13 в 10:50 * (equal * (eval +)) Charlz_Klug 28.08.13 в 03:16 |
||
|
4.2 Обратная Кавычка Charlz_Klug 28.08.13 в 08:45 |
||
|
Обратная кавычка, иногда называемая как квазикавычка (quasiquote) 2 и отображаемая как ` - это относительно новый элемент в промышленных диалектах Лиспа. Эта концепция по-прежнему абсолютно чужда для не-Лисп языков программирования. Charlz_Klug 28.08.13 в 08:53 |
||
|
2 Scheme программисты называют её квазикавычкой а Common Lisp программисты - обратной кавычкой. Charlz_Klug 28.08.13 в 08:54 |
||
|
У обратной кавычки странная история развития. Обратная кавычка развивалась параллельно с Лиспом. Есть сообщения [QUASIQUOTATION] о том, что раньше никто не верил в то, что вложенные обратные кавычки будут работать правильно, пока умные программисты не выяснили что обратные кавычки правильно выполняют свою работу - идеи людей о том, что было верным оказались неверными. Известно, что трудно понять работу вложенной обратной кавычки. Даже Стил (Steele), отец Common Lisp'а, жалуется на это [CLTL2—P530]. Charlz_Klug 28.08.13 в 09:09 |
||
|
В принципе Лисп не нуждается в обратной кавычке. Всё, что можно выполнить с помощью обратной кавычки - можно выполнять с помощью других функций, предназначенных для постройки списков. Однако, обратная кавычка настолько полезна при программировании макросов, а в Лиспе под программированием понимается программирование макросов, что Лисп профессионалы очень активно ею пользуются. Charlz_Klug 28.08.13 в 09:15 |
||
|
Вначале мы должны разобраться с обычным закавычиванием. В Лиспе, мы используем префикс формы в виде символа кавычки (') для того, чтобы информировать Лисп о том, что следующая форма должна рассматриваться как сырые данные, а не как код, который нужно вычислить. Или, если выразиться иначе, результатом вычисления кавычки в коде будет возвращение формы. Иногда мы говорим что кавычка останавливает или отключает вычисление формы. Charlz_Klug 28.08.13 в 09:26 |
||
|
В Лиспе обратная кавычка может использоваться как замена кавычки. Исключая некоторые специальные символы, называемые символами раскавычивания (unquote) и могущие появиться в форме, обратная кавычка останавливает вычисление тем-же способом, что и кавычка. Как следует из названия символы раскавычивания изменяют семантику вычисления. Иногда мы говорим что раскавычивание перезапускает или включает вычисление формы. Charlz_Klug 28.08.13 в 09:46 |
||
|
Есть три главных типа раскавычивания: обычное раскавычивание, объединяющее раскавычивание и деструктивное, объединяющее раскавычивание. Charlz_Klug 29.08.13 в 05:39 |
||
|
Для выполнения обычного раскавычивания, мы используем оператор запятую: Charlz_Klug 29.08.13 в 05:40 |
||
|
* (let ((s ’hello)) Charlz_Klug 29.08.13 в 05:41 |
||
|
Хотя выражение, которое мы раскавычили является простым вычислением символа, s, на месте этого символа может быть любое Лисп выражение, вычисляемое в нечто значимое для любого контекста, в котором оно появляется в шаблоне обратной кавычки. Каким бы ни был результат, он будет вставлен в результирующий список в car позиции того места в котором он появился в шаблоне обратной кавычки. Charlz_Klug 29.08.13 в 05:58 |
||
|
В нотации Лисповской формы мы можем использовать . если мы хотим явно вставить что-либо cdr'ное в создаваемую списковую структуру. Если здесь мы вставим список, то результирующей формой обратной кавычки будет корректный список. Но, если здесь мы вставим что-нибудь другое, то мы получим новую не-списковую структуру. Charlz_Klug 29.08.13 в 06:11 |
||
|
Такое-же поведение доступно нам везде, в том числе внутри обратной кавычки 3. Благодаря архитектуре обратной кавычки мы можем раскавычивать элементы даже в такой позиции: Charlz_Klug 29.08.13 в 06:14 |
||
|
3 Поскольку обратная кавычка использует (почти ту же) стандартную функцию read что и везде. Charlz_Klug 29.08.13 в 06:17 |
||
|
* (let ((s ’(b c d))) Charlz_Klug 29.08.13 в 06:17 |
||
|
Вставка списков в cdr позиции создаваемого списка из шаблона обратной кавычки оказалось весьма универсальным, поэтому был сделан следующий шаг названный как объединяющее раскавычивание. Вышеприведённая комбинация ., полезна, но не способна вставлять элементы в середину списка. Для этого у нас есть оператор объединяющее раскавычивание: Charlz_Klug 29.08.13 в 06:24 |
||
|
* (let ((s ’(b c d))) Charlz_Klug 29.08.13 в 10:29 |
||
|
Ни ., ни ,@ не модифицируют сращиваемый список. Для примера, после вычисления обратной кавычки в обеих предыдущих формах, s по прежнему будет привязан к трёх элементному списку (B C D). Хотя это не является строго определяемым стандартом, в форме (A B C D), (B C D) может являться общей структурой с объединённым списком s. Однако, структура списка (A B C D E) гарантирует, что она будет свежесозданной при вычислении обратной кавычки, поскольку ,@ запрещено модифицировать сращенные списки. Объединяющее раскавычивание - не деструктивная операция, поскольку в целом нам нужно думать об обратной кавычке как о компонуемом шаблоне для создания списков. Деструктивная модификация списковой структуры не свежесозданных данных при каждом вычислении обратно закавыченного кода может иметь нежелательные эффекты в плане будущих расширений. Charlz_Klug 29.08.13 в 10:46 |
||
|
Однако, в Common Lisp есть деструктивная версия сращиваемого раскавычивания, которое вы можете использовать везде, где можно использовать сращивающее раскавычивание. Для деструктивного раскавычивания используйте ,. . Деструктивное сращивание работает также, как и обычное сращивание, за исключением того, что сращиваемый список может быть модифицирован в процессе вычисления шаблона обратной кавычки. И хотя отличие от обычного раскавычивания выражается всего лишь в одном символе, эта нотация более умно используем символ . из cdr позиционного раскавычивания ., рассмотренного выше. Charlz_Klug 29.08.13 в 10:56 |
||
|
Для того, чтобы увидеть это в действии в этом примере мы деструктивно модифицируем список указанный в to-splice: Charlz_Klug 29.08.13 в 10:58 |
||
|
* (defvar to-splice ’(B C D)) Charlz_Klug 29.08.13 в 10:59 |
||
|
Деструктивная модификация сращиваемых списков может быть опасной. Рассмотрим следующее применение деструктивного сращивания: Charlz_Klug 30.08.13 в 09:12 |
||
|
(defun dangerous-use-of-bq () Charlz_Klug 30.08.13 в 09:12 |
||
|
При первом вызове dangerous-use-of-bq возвращается ожидаемый ответ: (A B C D E). Но, поскольку эта функция использует деструктивное сращивание и модификацию не свежесгенерированных списков - закавыченный список - то мы можем ожидать возникновения различных нежелательных последствий. В этом случае при втором вычислении dangerous-use-of-bq форма (B C D) будет представлять из себя форму (B C D E) и в момент, когда обратная кавычка попытается деструктивно срастить этот список с остатком шаблона обратной кавычки (E) - его собственный хвост - будет создан список, содержащий цикл (cycle). Более детально мы обсудим циклы в разделе 4.5, Циклические Выражения. Charlz_Klug 30.08.13 в 09:23 |
||
|
Однако, есть много случаев в которых деструктивное сращивание является чрезвычайно безопасным. Не позволяйте dangerous-use-of-bq напугать вас если вам нужна повышенная эффективность в ваших формах обратной кавычки. Есть множество операций, создающих свежие списковые структуры, которые вам так или иначе могут не понадобиться. Например, сращивание результатов mapcar настолько распространено и безопасно, что вполне может претендовать на программную идиому: Charlz_Klug 30.08.13 в 09:31 |
||
|
(defun safer-use-of-bq () Charlz_Klug 30.08.13 в 09:32 |
||
|
Но, есть деталь, которая мешает этому. Чаще всего обратная кавычка используется для создания макросов, часть программирования на Лиспе, где менее важна скорость и более важна ясность. Если думать о побочных эффектах в ваших операциях сращивания, то при создании и интерпретации макросов вам придётся часто отвлекаться на них, а это не стоит свеч. Эта книга придерживается обычных сращиваний. Чаще всего обратная кавычка используется для конструирования макросов, но, это не единственное их использование. Обратная кавычка - это удобный предметно ориентированный язык для смешивания списков, и ещё более удобным он становится с возможностью деструктивного сращивания. Charlz_Klug 30.08.13 в 09:45 |
||
|
Как работает обратная кавычка? Обратная кавычка - это считывающий макрос. Формы обратной кавычки читаются как код, который при вычислении, становится желаемым списком. Возвращаясь к примеру из предыдущего раздела о вычислении во время выполнения, мы можем отключить красивую печать (pretty printing), закавычить значение формы обратной кавычки и вывести его на печать для того, чтобы увидеть как читаются формы с обратной кавычкой 4: Charlz_Klug 30.08.13 в 09:53 |
||
|
4 Мы возвращаем t и поэтому мы не видим значения, возвращаемого от print. (values) также универсальны. Charlz_Klug 30.08.13 в 09:54 |
||
|
* (let (*print-prettyt) ; привязано к nil Charlz_Klug 30.08.13 в 09:56 |
||
|
В предыдущей некрасиво напечатанной форму, функция LISP: :BACKQ-LIST идентична list, за исключением поведения, связанного с красивой печатью. Заметьте, что операторы запятая исчезли. Common Lisp очень либерален и поэтому позволяет читать обратные кавычки, также, как и допускает операции с использованием общих структур. Charlz_Klug 30.08.13 в 10:02 |
||
|
Кроме того обратная кавычка предоставляет много интересных решений забавной проблемы написания Лисп выражений, вычисляющихся в самих себя. Эти выражения часто называются куайнами в честь Уилларда Куайна, который занимался их широким изучением и кто, по сути, ввёл термин квазицитирования - альтернативное название обратной кавычки [FOUNDATIONS-P31-FOOTNOTE3]. Вот забавный пример куайна, связанный с Майком МкМахоном (Mike McMahon) [QUASIQUOTATION]: Charlz_Klug 02.09.13 в 09:40 |
||
|
* (let ((let `'(let ((let ’,let)) Charlz_Klug 04.09.13 в 07:56 |
