У меня ограниченные знания Lisp (я стараюсь немного поучиться в свободное время), но насколько я понимаю, макросы Lisp позволяют вводить новые языковые конструкции и синтаксис, описывая их в самом Lisp. Это означает, что новая конструкция может быть добавлена в виде библиотеки без изменения компилятора / интерпретатора Lisp.
Этот подход сильно отличается от других языков программирования. Например, если бы я хотел расширить Паскаль с помощью нового вида цикла или какой-то конкретной идиомы, мне пришлось бы расширить синтаксис и семантику языка и затем реализовать эту новую функцию в компиляторе.
Существуют ли другие языки программирования за пределами семейства Lisp (например, кроме Common Lisp, Scheme, Clojure (?), Racket (?) И т. Д.), Которые предлагают аналогичную возможность расширения языка внутри самого языка?
РЕДАКТИРОВАТЬ
Пожалуйста, избегайте расширенного обсуждения и будьте конкретны в своих ответах. Вместо длинного списка языков программирования, которые могут быть так или иначе расширены, я хотел бы понять с концептуальной точки зрения, что является специфическим для макросов Lisp как механизма расширения, и какие языки программирования, не относящиеся к Lisp, предлагают некоторую концепцию это близко к ним.
источник
Ответы:
Scala также делает это возможным (фактически, он был сознательно разработан для поддержки определения новых языковых конструкций и даже полных DSL).
Помимо функций высшего порядка, лямбда-выражений и карри, которые распространены в функциональных языках, здесь есть несколько специальных языковых функций, позволяющих это *:
a.and(b)
есть эквивалентноa and b
в инфиксной формедля однопараметрических вызовов функций вы можете использовать фигурные скобки вместо обычных скобок - это (вместе с каррингом) позволяет писать такие вещи, как
где
withPrintWriter
простой метод с двумя списками параметров, каждый из которых содержит один параметрmyAssert(() => x > 3)
в более короткой форме, какmyAssert(x > 3)
Создание примера DSL подробно обсуждается в главе 11. Предметно-ориентированные языки в Scala бесплатной книги « Программирование Scala» .
* Я не имею в виду, что они уникальны для Scala, но, по крайней мере, они не очень распространены. Я не эксперт в функциональных языках, хотя.
источник
Perl допускает предварительную обработку своего языка. Хотя это не часто используется для радикального изменения синтаксиса в языке, это можно увидеть в некоторых из ... странных модулей:
Также существует модуль, который позволяет perl запускать код, который выглядит так, как будто он написан на Python.
Более современный подход к этому в perl заключается в использовании Filter :: Simple (один из основных модулей в perl5).
Обратите внимание, что все эти примеры касаются Дамиана Конвея, которого называют «Безумный доктор Перла». Это все еще удивительно мощная способность в Perl крутить язык так, как он этого хочет.
Дополнительная документация для этой и других альтернатив существует в perlfilter .
источник
Haskell
На Haskell есть «Шаблон Haskell», а также «Квазиквотация»:
http://www.haskell.org/haskellwiki/Template_Haskell
http://www.haskell.org/haskellwiki/Quasiquotation
Эти функции позволяют пользователям существенно добавлять синтаксис языка за пределы обычных средств. Они также разрешаются во время компиляции, что, я думаю, является обязательным условием (по крайней мере, для скомпилированных языков) [1].
Я использовал квази-цитату в Haskell один раз, чтобы создать расширенный сопоставитель шаблонов на C-подобном языке:
[1] Иначе следующее квалифицируется как расширение синтаксиса:
runFeature "some complicated grammar enclosed in a string to be evaluated at runtime"
что, конечно, является грузом дерьма.источник
forM
).Tcl имеет долгую историю поддержки расширяемого синтаксиса. Например, вот реализация цикла, который перебирает три переменные (до остановки) по кардиналам, их квадратам и кубам:
Это будет тогда использоваться так:
Этот вид техники широко используется в программировании на Tcl, и ключом к его разумному выполнению являются команды
upvar
anduplevel
(upvar
связывает именованную переменную в другой области действия с локальной переменной иuplevel
запускает сценарий в другой области действия: в обоих случаях1
указывает, что рассматриваемая область является вызывающей стороной). Он также часто используется в коде, который соединяется с базами данных (запускает некоторый код для каждой строки в наборе результатов), в Tk для GUI (для привязки обратных вызовов к событиям) и т. Д.Однако это только часть того, что сделано. Встроенный язык даже не должен быть Tcl; это может быть практически что угодно (пока он уравновешивает свои фигурные скобки - вещи становятся синтаксически ужасными, если это не так - что является огромным большинством программ), и Tcl может просто отправлять на встроенный иностранный язык по мере необходимости. Примеры этого включают в себя внедрение C для реализации команд Tcl и эквивалент с Fortran. (Возможно, все встроенные команды Tcl выполняются таким образом, в том смысле, что они на самом деле просто стандартная библиотека, а не сам язык.)
источник
Это частично вопрос семантики. Основная идея Lisp заключается в том, что программа - это данные, которыми можно манипулировать. Обычно используемые языки в семействе Lisp, такие как Scheme, на самом деле не позволяют добавлять новый синтаксис в смысле синтаксического анализатора; это всего лишь разделенные пробелами списки. Просто синтаксис ядра делает так мало, что из него можно сделать практически любую семантическую конструкцию. Scala (обсуждается ниже) похожа: правила имен переменных настолько либеральны, что вы можете легко сделать из них хорошие DSL (оставаясь в рамках тех же основных синтаксических правил).
Эти языки, хотя на самом деле они не позволяют вам определять новый синтаксис в смысле фильтров Perl, имеют достаточно гибкое ядро, которое вы можете использовать для создания DSL и добавления языковых конструкций.
Важной общей особенностью является то, что они позволяют вам определять языковые конструкции, которые работают так же, как и встроенные, используя функции, предоставляемые языками. Степень поддержки этой функции варьируется:
sin()
,round()
и т. Д., Без какого-либо способа реализовать свои собственные.static_cast<target_type>(input)
,dynamic_cast<>()
,const_cast<>()
,reinterpret_cast<>()
) можно эмулировать с помощью функции шаблона, стимулирущие использует дляlexical_cast<>()
,polymorphic_cast<>()
,any_cast<>()
, ....for(;;){}
,while(){}
,if(){}else{}
,do{}while()
,synchronized(){}
,strictfp{}
) и не позволяет определить свои собственные. Вместо этого Scala определяет абстрактный синтаксис, который позволяет вызывать функции с использованием удобного синтаксиса, похожего на структуру элемента управления, и библиотеки используют его для эффективного определения новых структур управления (например,react{}
в библиотеке актеров).Кроме того, вы можете взглянуть на пользовательский синтаксис Mathematica в пакете Notation . (Технически это относится к семейству Lisp, но имеет некоторые возможности расширения, сделанные по-другому, а также обычную расширяемость Lisp.)
источник
(defmacro ...)
. На самом деле я сейчас портирую этот язык на Racket, просто для удовольствия. Но я согласен, что это что-то не очень полезное, поскольку синтаксиса S-выражений более чем достаточно для большинства возможных полезных семантических s.(define-macro ...)
эквивалент, который, в свою очередь, может использовать любой вид анализа внутри системы.Rebol звучит почти как то, что вы описываете, но немного сбоку.
Вместо того, чтобы определять конкретный синтаксис, все в Rebol - это вызов функции - здесь нет ключевых слов. (Да, вы можете переопределить
if
иwhile
если вы действительно хотите). Например, этоif
утверждение:if
это функция, которая принимает 2 аргумента: условие и блок. Если условие истинно, блок оценивается. Похоже на большинство языков, верно? Ну, блок - это структура данных, он не ограничен кодом - это, например, блок блоков и быстрый пример гибкости «код - данные»:Пока вы можете придерживаться правил синтаксиса, расширение этого языка, по большей части, будет не чем иным, как определением новых функций. Некоторые пользователи перенесли функции Rebol 3 в Rebol 2, например.
источник
У Ruby довольно гибкий синтаксис, я думаю, что это способ «расширить язык внутри самого языка».
Примером являются грабли . Он написан на Ruby, это Ruby, но выглядит как make .
Чтобы проверить некоторые возможности, вы можете поискать ключевые слова Ruby и метапрограммирование .
источник
Расширение синтаксиса так, как вы говорите, позволяет создавать языки, специфичные для предметной области. Поэтому, возможно, самый полезный способ перефразировать ваш вопрос: какие другие языки имеют хорошую поддержку для языков, специфичных для предметной области?
У Ruby очень гибкий синтаксис, и там процветает множество DSL, таких как rake. Groovy включает в себя много этого добра. Он также включает в себя преобразования AST, которые более прямо аналогичны макросам Lisp.
R, язык для статистических вычислений, позволяет функциям получать свои аргументы без оценки. Он использует это для создания DSL для определения формулы регрессии. Например:
означает «подгонка строки вида k0 + k1 * a + k2 * b к значениям в y».
означает «соответствовать строке вида k0 + k1 * a + k2 * b + k3 * a * b значениям в y».
И так далее.
источник
Конвергенция - это еще один язык без метаний. И, в некоторой степени, C ++ тоже подходит.
Возможно, MetaOCaml довольно далеко от Lisp. Для совершенно другого стиля расширения синтаксиса, но все же достаточно мощного, взгляните на CamlP4 .
Nemerle - еще один расширяемый язык с метапрограммированием в стиле Lisp, хотя он ближе к таким языкам, как Scala.
И сама Scala скоро тоже станет таким языком.
Редактировать: я забыл о самом интересном примере - JetBrains MPS . Это не только очень далеко от всего Лиспиша, это даже нетекстовая система программирования с редактором, работающим непосредственно на уровне AST.
Edit2: чтобы ответить на обновленный вопрос - нет ничего уникального и исключительного в макросах Lisp. Теоретически, любой язык может обеспечить такой механизм (я даже сделал это с простым C). Все, что вам нужно, это доступ к вашему AST и возможность выполнять код во время компиляции. Может помочь некоторое размышление (запрос о типах, существующих определениях и т. Д.).
источник
Пролог позволяет определять новые операторы, которые переводятся в составные термины с тем же именем. Например, это определяет
has_cat
оператор и определяет его как предикат для проверки, содержит ли список атомcat
:В
xf
означает , чтоhas_cat
постфиксный оператор; использованиеfx
сделало бы его префиксным оператором иxfx
сделало бы его инфиксным оператором, принимающим два аргумента. Проверьте эту ссылку для более подробной информации об определении операторов в Прологе.источник
TeX полностью отсутствует в списке. Вы все это знаете, верно? Это выглядит примерно так:
... за исключением того, что вы можете переопределить синтаксис без ограничений. Каждому (!) Токену в языке может быть присвоено новое значение. ConTeXt - это макропакет, который заменил фигурные скобки квадратными скобками:
Более распространенный пакет макросов LaTeX также переопределяет язык для своих целей, например, добавляя
\begin{environment}…\end{environment}
синтаксис.Но это не останавливается там. Технически, вы также можете переопределить токены, чтобы проанализировать следующее:
Да, абсолютно возможно. Некоторые пакеты используют это для определения небольших специфичных для домена языков. Например, пакет TikZ определяет краткий синтаксис для технических чертежей, который позволяет следующее:
Кроме того, TeX завершен по Тьюрингу, так что вы можете буквально делать все с ним. Я никогда не видел, чтобы это использовало весь свой потенциал, потому что это было бы довольно бессмысленно и очень замысловато, но вполне возможно сделать следующий код разбираемым, просто переопределив токены (но это, вероятно, пойдет до физических ограничений синтаксического анализатора, из-за как оно построено)
источник
Boo позволяет настраивать язык во время компиляции с помощью синтаксических макросов.
У Boo есть «расширяемый конвейер компилятора». Это означает, что компилятор может вызывать ваш код для выполнения преобразований AST в любой точке конвейера компилятора. Как вы знаете, такие вещи, как Generics в Java или Linq в C #, являются просто синтаксическими преобразованиями во время компиляции, так что это довольно мощно.
По сравнению с Лиспом главное преимущество заключается в том, что это работает с любым синтаксисом. Boo использует Python-вдохновленный синтаксис, но вы, вероятно, могли бы написать расширяемый компилятор с синтаксисом C или Pascal. А поскольку макрос оценивается во время компиляции, производительность не снижается.
Недостатками по сравнению с Лиспом являются:
Например, вот как вы можете реализовать новую структуру управления:
использование:
который затем переводится во время компиляции в нечто вроде:
(К сожалению, онлайн-документация Boo всегда безнадежно устарела и даже не охватывает такие сложные вещи, как этот. Лучшая документация для языка, который я знаю, это книга: http://www.manning.com/rahien/ )
источник
Оценка Mathematica основана на сопоставлении с образцом и его замене. Это позволяет вам создавать свои собственные структуры управления, изменять существующие структуры управления или изменять способ оценки выражений. Например, вы можете реализовать «нечеткую логику» следующим образом (немного упрощенно):
Это переопределяет оценку для предопределенных логических операторов &&, || ,! и встроенная
If
оговорка.Вы можете прочитать эти определения как определения функций, но реальное значение таково: если выражение соответствует шаблону, описанному слева, оно заменяется выражением справа. Вы можете определить свое собственное предложение If следующим образом:
SetAttributes[..., HoldRest]
говорит оценщику, что он должен оценить первый аргумент перед сопоставлением с образцом, но оставить оценку для остальных до тех пор, пока образец не будет сопоставлен и заменен.Это широко используется в стандартных библиотеках Mathematica, например, для определения функции,
D
которая принимает выражение и вычисляет его символьную производную.источник
Metalua - это язык и компилятор, совместимый с Lua, который обеспечивает это.
Отличия от Лисп:
Примером применения является реализация ML-подобного сопоставления с образцом.
Смотрите также: http://lua-users.org/wiki/MetaLua
источник
Если вы ищете расширяемые языки, вам стоит взглянуть на Smalltalk.
В Smalltalk единственным способом программирования является расширение языка. Нет никакой разницы между IDE, библиотеками или самим языком. Они все так переплетены, что Smalltalk часто называют скорее средой, а не языком.
Вы не пишете автономные приложения в Smalltalk, вместо этого вы расширяете языковую среду.
Проверьте http://www.world.st/ для нескольких ресурсов и информации.
Я хотел бы рекомендовать Pharo как диалект входа в мир Smalltalk: http://pharo-project.org
Надеюсь, это помогло!
источник
Существуют инструменты, позволяющие создавать собственные языки без написания всего компилятора с нуля. Например, есть Spoofax , который является инструментом преобразования кода: вы вводите входную грамматику и правила преобразования (написанные декларативным способом на очень высоком уровне), а затем вы можете генерировать исходный код Java (или другой язык, если вам это нужно) от пользовательского языка, разработанного вами.
Таким образом, можно было бы взять грамматику языка X, определить грамматику языка X '(X с вашими собственными расширениями) и преобразование X' → X, и Spoofax сгенерирует компилятор X '→ X.
В настоящее время, если я правильно понимаю, лучшая поддержка для Java, с развитием поддержки C # (или я так слышал). Этот метод может быть применен к любому языку со статической грамматикой (например, возможно, не к Perl ).
источник
Forth - другой язык, который является очень расширяемым. Многие реализации Forth состоят из небольшого ядра, написанного на ассемблере или C, тогда остальная часть языка написана на самом Forth.
Есть также несколько основанных на стеке языков, которые вдохновлены Forth и разделяют эту функцию, такие как Factor .
источник
Funge-98
Функция распознавания отпечатков пальцев в Funge-98 позволяет полностью реструктурировать весь синтаксис и семантику языка. Но только в том случае, если разработчик предоставляет механизм распознавания отпечатков пальцев, который позволяет пользователю программно изменять язык (это теоретически возможно реализовать в рамках нормального синтаксиса и семантики Funge-98). Если это так, можно буквально заставить остальную часть файла (или любые другие части файла) действовать как C ++ или Lisp (или все, что он хочет).
http://quadium.net/funge/spec98.html#Fingerprints
источник
Чтобы получить то, что вы ищете, вам действительно нужны эти скобки и отсутствие синтаксиса. Несколько основанных на синтаксисе языков могут быть близки, но это не совсем то же самое, что настоящий макрос.
источник