Как Lisp позволяет вам переопределить сам язык? - PullRequest
60 голосов
/ 22 февраля 2010

Я слышал, что Lisp позволяет вам переопределить сам язык, и я попытался исследовать его, но нигде нет четкого объяснения. У кого-нибудь есть простой пример?

Ответы [ 7 ]

86 голосов
/ 22 февраля 2010

Пользователи Lisp называют Lisp как программируемый язык программирования . Используется для символьных вычислений - для вычислений с символами.

Макросы - это только один из способов использования символической вычислительной парадигмы. Более широкое видение заключается в том, что Lisp предоставляет простые способы описания символических выражений: математических терминов, логических выражений, итерационных операторов, правил, описаний ограничений и многого другого. Макросы (преобразования исходных форм Lisp) являются лишь одним из приложений символьных вычислений.

В этом есть определенные аспекты: если вы спросите о «переопределении» языка, то переопределение строго будет означать переопределение существующего языкового механизма (синтаксис, семантика, прагматика). Но есть также расширение, встраивание, удаление языковых возможностей.

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

Несколько способов переопределить / изменить / расширить функциональность в соответствии с основными реализациями Common Lisp:

  • Синтаксис s-выражения . Синтаксис s-выражений не фиксирован. Считыватель (функция READ) использует так называемые таблицы чтения для указания функций, которые будут выполняться при чтении символа. Можно изменять и создавать таблицы чтения. Это позволяет вам, например, изменить синтаксис списков, символов или других объектов данных. Можно также ввести новый синтаксис для новых или существующих типов данных (например, хеш-таблиц). Также возможно полностью заменить синтаксис s-выражения и использовать другой механизм синтаксического анализа. Если новый синтаксический анализатор возвращает формы Lisp, для интерпретатора или компилятора не требуется никаких изменений. Типичным примером является макрос чтения, который может читать выражения инфикса. В таком макросе чтения используются инфиксные выражения и правила приоритета для операторов. Макросы чтения отличаются от обычных макросов: макросы чтения работают на уровне символов синтаксиса данных Lisp.

  • замена функций . Функции верхнего уровня связаны с символами. Пользователь может изменить эту привязку. В большинстве реализаций есть механизм, позволяющий сделать это даже для многих встроенных функций. Если вы хотите предоставить альтернативу встроенной функции ROOM, вы можете заменить ее определение. Некоторые реализации вызовут ошибку, а затем предложат возможность продолжить изменение. Иногда это необходимо, чтобы разблокировать пакет. Это означает, что функции в целом могут быть заменены новыми определениями. Есть ограничения к этому. Одним из них является то, что компилятор может встроить функции в коде. Чтобы увидеть эффект, нужно перекомпилировать код, который использует измененный код.

  • функции консультирования . Часто хочется добавить некоторое поведение к функциям. Это называется «консультирование» в мире Lisp. Многие реализации Common Lisp предоставляют такую ​​возможность.

  • нестандартные пакеты . Пакеты группируют символы в пространствах имен. Пакет COMMON-LISP является домом для всех символов, которые являются частью стандарта ANSI Common Lisp. Программист может создавать новые пакеты и импортировать существующие символы. Таким образом, вы можете использовать в своих программах пакет EXTENDED-COMMON-LISP, который предоставляет больше или другие возможности. Просто добавив (IN-PACKAGE "EXTENDED-COMMON-LISP"), вы можете начать разработку, используя собственную расширенную версию Common Lisp. В зависимости от используемого пространства имен используемый вами диалект Lisp может выглядеть незначительно или даже радикально отличаться. В Genera на Lisp Machine есть несколько диалектов Lisp, расположенных рядом друг с другом: ZetaLisp, CLtL1, ANSI Common Lisp и Symbolics Common Lisp.

  • CLOS и динамические объекты. Common Lisp Object System поставляется со встроенными изменениями. Протокол мета-объектов расширяет эти возможности. Сам CLOS может быть расширен / переопределен в CLOS. Вы хотите другое наследство. Напишите метод. Вы хотите разные способы хранения экземпляров. Напишите метод. Слоты должны иметь больше информации. Обеспечить класс для этого. Сам CLOS спроектирован так, что он способен реализовать целый «регион» различных объектно-ориентированных языков программирования. Типичными примерами являются добавление таких вещей, как прототипы, интеграция с системами внешних объектов (например, Objective C), добавление стойкости, ...

  • Лисп формы . Интерпретация форм Lisp может быть переопределена с помощью макросов. Макрос может проанализировать исходный код и изменить его. Существуют различные способы управления процессом трансформации. Сложные макросы используют обходчик кода, который понимает синтаксис форм Lisp и может применять преобразования. Макросы могут быть тривиальными, но также могут быть очень сложными, как макросы LOOP или ITERATE. Другими типичными примерами являются макросы для встроенного SQL и генерация встроенного HTML. Макросы также могут использоваться для перемещения вычислений во время компиляции. Поскольку компилятор сам является программой на Лиспе, во время компиляции могут выполняться произвольные вычисления. Например, макрос Lisp может вычислить оптимизированную версию формулы, если во время компиляции известны определенные параметры.

  • Символы . Common Lisp предоставляет макросы символов. Макросы символов позволяют изменить значение символов в исходном коде. Типичный пример: (with-slots (foo) bar (+ foo 17)) Здесь символ FOO в источнике, заключенном в WITH-SLOTS, будет заменен вызовом (bar-value bar 'foo).

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

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

  • Специальные переменные - добавить привязки переменных в существующий код. Многие диалекты Lisp, такие как Common Lisp, предоставляют специальные / динамические переменные. Их значение ищется во время выполнения в стеке. Это позволяет включать код для добавления привязок переменных, которые влияют на существующий код, не изменяя его. Типичным примером является переменная типа * standard-output *. Можно перепривязать переменную, и все выходные данные, использующие эту переменную во время динамической области нового связывания, пойдут в новом направлении. Ричард Столлман утверждал, что для него было очень важно, чтобы в Emacs Lisp было установлено значение по умолчанию (хотя Столлман знал о лексическом связывании в Scheme и Common Lisp).

Lisp имеет эти и другие возможности, потому что он использовался для реализации множества различных языков и парадигм программирования. Типичным примером является встроенная реализация логического языка, скажем, Prolog. Lisp позволяет описывать термины Prolog с помощью s-выражений и с помощью специального компилятора, термины Prolog могут быть скомпилированы в код на Lisp. Иногда требуется обычный синтаксис Prolog, тогда парсер будет анализировать типичные термины Prolog в формы Lisp, которые затем будут компилироваться. Другими примерами встроенных языков являются языки на основе правил, математические выражения, термины SQL, встроенный ассемблер Lisp, HTML, XML и многие другие.

14 голосов
/ 22 февраля 2010

Я расскажу о том, что Scheme отличается от Common Lisp, когда речь идет об определении нового синтаксиса. Это позволяет вам определять шаблоны, используя define-syntax, которые применяются к вашему исходному коду, где бы они ни использовались. Они выглядят так же, как функции, только они запускаются во время компиляции и преобразуют AST.

Вот пример того, как let можно определить в терминах lambda. Строка с let является шаблоном для сопоставления, а строка с lambda является результирующим шаблоном кода.

(define-syntax let
  (syntax-rules ()
    [(let ([var expr] ...) body1 body2 ...)
     ((lambda (var ...) body1 body2 ...) expr ...)]))

Обратите внимание, что это НИЧЕГО, как текстовая замена. На самом деле вы можете переопределить lambda, и приведенное выше определение для let все равно будет работать, поскольку оно использует определение lambda в среде, где было определено let. По сути, он мощный, как макросы, но чистый, как функции.

4 голосов
/ 22 февраля 2010

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

Макросы - не совсем полное переопределение языка, по крайней мере, насколько я знаю (на самом деле я - Schemer; я могу ошибаться), потому что есть ограничение. Макрос может взять только одно поддерево вашего кода и сгенерировать одно поддерево для его замены. Поэтому вы не можете писать макросы, преобразующие всю программу, как бы круто это ни было.

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

3 голосов
/ 19 февраля 2014

Ссылка на главу 4-5 «Структура и интерпретация компьютерных программ» - это то, чего мне не хватало в ответах ( ссылка ).

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

2 голосов
/ 19 ноября 2013

Классный пример на http://www.cs.colorado.edu/~ralex/papers/PDF/X-expressions.pdf

Макросы

для чтения определяют X-выражения для сосуществования с S-выражениями, например,

? (cx <circle cx="62" cy="135" r="20"/>) 
62

Обычный ванильный обыкновенный Лисп на http://www.AgentSheets.com/lisp/XMLisp/XMLisp.lisp ...

(eval-when (:compile-toplevel :load-toplevel :execute)
  (when (and (not (boundp '*Non-XMLISP-Readtable*)) (get-macro-character #\<))
    (warn "~%XMLisp: The current *readtable* already contains a #/< reader function: ~A" (get-macro-character #\<))))

... конечно, синтаксический анализатор XML не так прост, но подключить его к считывателю lisp.

2 голосов
/ 14 февраля 2011

Со стороны смотрит в ...

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

Это не так уж много, что он может переопределить себя, поскольку его базовое определение настолько податливо, что оно может принимать любую форму, и что никакая форма не принимается / не предполагается в структуре.

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

Я думаю .....

2 голосов
/ 22 февраля 2010

Этот ответ конкретно касается Common Lisp (далее CL), хотя части ответа могут быть применимы к другим языкам в семействе lisp.

Поскольку CL использует S-выражения и (в основном) выглядит как последовательность приложений функций, нет очевидной разницы между встроенными модулями и пользовательским кодом. Основное отличие состоит в том, что «то, что обеспечивает язык», доступно в специальном пакете в среде программирования.

С некоторой осторожностью, нетрудно кодировать замены и использовать их вместо этого.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...