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

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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

С небольшим вниманием, нетрудно заменить код и использовать их.

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

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

Ответ 6

С внешней стороны, глядя в...

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

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

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

Я думаю.....

Ответ 7

Прохладный пример в http://www.cs.colorado.edu/~ralex/papers/PDF/X-expressions.pdf

Макросы чтения

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

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

plain vanilla 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.