Как Lisp позволяет вам переопределить сам язык?


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

7 60

7 ответов:

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

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

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

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

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

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

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

  • советую функций. Часто требуется добавить некоторое поведение к функциям. Это называется "советовать" в мире Lisp. Много Общего Шепелявит реализация обеспечит такую возможность.

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

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

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

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

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

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

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

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

Я собираюсь передать эту схему, которая отличается от 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 в среду, где . В принципе, он мощный, как макросы, но чистый, как функции.

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

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

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

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

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

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

поскольку CL использует s-выражения и (в основном) выглядит как последовательность приложений функций, нет очевидной разницы между встроенными и пользовательским кодом. Основное различие заключается в том, что "вещи, которые предоставляет язык", доступны в определенном пакете в среде кодирования.

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

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

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

снаружи, заглядывая внутрь...

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

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

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

Я думаю.....

классный пример на http://www.cs.colorado.edu / ~ralex / документы / PDF / X-expressions. pdf

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

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

обыкновенная ваниль Common Lisp at 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 есть.