Есть ли диалекты, не относящиеся к Лиспу, которые допускают синтаксическую абстракцию? - PullRequest
12 голосов
/ 27 июня 2011

Как говорит Рич Хики, секретный язык языков Лисп - это возможность напрямую манипулировать абстрактным синтаксическим деревом с помощью макросов. Может ли это быть достигнуто на любых не-Лиспских языках?

Ответы [ 6 ]

22 голосов
/ 27 июня 2011

Возможность «напрямую манипулировать абстрактным синтаксическим деревом» сама по себе не является чем-то новым, хотя это очень мало в разных языках. Например, многие языки в наши дни имеют какую-то функцию eval, но должно быть очевидно, что это , а не манипулирование синтаксическим деревом abstract , вместо этого это манипуляция синтаксиса concrete - прямой исходный код. Кстати, упомянутая функциональность в D подпадает под ту же категорию, что и CPP: оба имеют дело с необработанным исходным текстом.

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

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

Теперь, если смотреть в этом свете, что-то вроде объекта D очень похоже на природу. Но тот факт, что он имеет дело с необработанным текстом, а не с AST, ограничивает его полезность. Если вы посмотрите на пример на этой странице,

mixin(GenStruct!("Foo", "bar"));

вы можете видеть, как это не выглядит как часть языка - чтобы сделать его более похожим на Lisp, вы бы использовали его естественным образом:

GenStruct(Foo, bar);

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

template expression GenStruct(identifier Name, identifier M1) {
    return [[struct $Name$ { int $M1$; }; ]]
}

Одна важная вещь, которую следует здесь отметить, состоит в том, что, поскольку D является языком со статической типизацией, AST явно вползли в это умственное упражнение - как типы identifier и expression (я предполагаю, что template помечает это как определение макроса, но для него все еще требуется тип возвращаемого значения).

В Лиспе вы получаете что-то очень похожее на эту функциональность, а не на плохое решение для работы со строками. Но вы получаете еще больше - Lisp преднамеренно изменяет базовый тип списка и объединяет AST с языком времени выполнения очень простым способом: AST состоит из символов и списков и других базовых литералов (чисел, строк, логических значений), и все это часть языка времени выполнения. Фактически, для этих литералов Lisp делает еще один шаг вперед и использует литералы в качестве собственного синтаксиса - например, число 123 (значение, которое существует во время выполнения) представляется синтаксисом, который также равен число 123 (но теперь это значение, которое существует во время компиляции). Суть в том, что код, связанный с макросами, в Лиспе, как правило, гораздо проще иметь дело, чем то, что другие языки называют «макросами». Представьте, например, что в примере кода D создаются поля N int в структуре (где N - новый вход для макроса) - для этого потребуется использовать некоторую функцию для перевода строки в число.

6 голосов
/ 27 июня 2011

Lisp

Причины, по которым LISP является "особенным", ...

Встроенная функциональность очень экономична:

  • Единственными встроенными структурами данных являются атомы или списки
  • Синтаксис реализован в терминах структуры данных списка
  • Очень мало "системных функций"

Он поддерживает функции таким образом, что новые определения функций неотличимы от встроенных функций:

  • Синтаксис вызова идентичен
  • Оценка аргументов может полностью контролироваться

Он поддерживает макросы таким образом, что произвольный код на Лиспе всегда можно определить в терминах предметно-ориентированного языка:

  • Синтаксис вызова аналогичен пользовательской функции-Синтаксис вызова, аналогичный встроенному синтаксису вызова функции
  • Оценка аргументов полностью управляема
  • Возможно генерирование произвольного кода на Лиспе
  • Макросы оцениваются во время выполненияТаким образом, реализация макроса может вызывать существующий код во время генерации нового кода

С помощью вышеуказанных функций вы можете:

  • Повторно реализовать Lisp-inside-Lisp, в оченьмаленький код
  • Добавить любые существующие идиомы программирования таким образом, который неотличим от встроенных функций

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

Другие языки

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

Некоторые примеры:

В Boo вы можете использовать синтаксические макросы для определения новых DSL, которые будут автоматически обрабатываться компилятором.Благодаря этому вы можете реализовать любую языковую функцию поверх существующих.Ограничение по сравнению с Lisp заключается в том, что они оцениваются во время компиляции, поэтому генерация кода во время выполнения напрямую не поддерживается.

В Javascript структуры данных являются универсальными и гибкими (все это либо встроенный тип, либо ассоциативный массив).Он также поддерживает вызов функций непосредственно из ассоциативных массивов.Благодаря этому вы можете реализовать несколько языковых функций поверх существующих, таких как классы и пространства имен.

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

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

Lua очень похож на Javascript вбольшинство из этих способов.

Препроцессор C ++ позволяет вам определять свой собственный DSL с помощьюСхожий синтаксис с существующими вызовами функций.Он не позволяет вам контролировать оценку (которая является источником множества ошибок и почему большинство людей говорит C/C++ macros are "Evil"), но он поддерживает несколько ограниченную форму генерации кода.

Поддержка генерации кода в макросах C / C ++ ограничена, поскольку макросы оцениваются до компиляции кода и не могут контролироваться с помощью кода C.Это почти полностью ограничено текстовой заменой.Это значительно ограничивает тип кода, который может быть сгенерирован.

Функция шаблона C ++ довольно мощная (от WRT до C /Макросы C ++) для синтаксических дополнений к языку.Он может превратить большую часть оценки кода во время выполнения в оценку кода во время компиляции и может делать статические утверждения для вашего существующего кода.Он может ссылаться на существующий код C ++ ограниченным образом.

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

Обратите внимание, что это не помешало метапрограммированию шаблонов стать активной областью исследований во многих сообществах.См. Проект boost , значительная часть которого посвящена библиотекам с поддержкой TMP и библиотекам с поддержкой TMP.

Утиная печать позволяет определять синтаксис для объектов, который позволяет заменять реализации во время выполнения.Это похоже на то, как Javascript определяет функции на ассоциативных массивах.

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

5 голосов
/ 28 июня 2011

Ради полноты, в дополнение к уже упомянутым языкам и препроцессорам:

3 голосов
/ 27 июня 2011

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

2 голосов
/ 27 июня 2011

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

1 голос
/ 27 июня 2011

Я бы сказал, Tcl соответствует требованиям - хорошо, в зависимости от того, считаете ли вы Tcl Лиспом или нет.

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

...