Согласно этой статье следующая строка кода на Лиспе выводит «Hello world» на стандартный вывод.
(format t "hello, world")
Lisp, который является гомоиконическим языком , может обрабатывать код как данные следующим образом:
Теперь представьте, что мы написали следующий макрос:
(defmacro backwards (expr) (reverse expr))
в обратном направлении - это имя макроса, который принимает выражение (представленное в виде списка) и обращает его в обратном порядке. Вот снова «Hello, world», на этот раз с помощью макроса:
(backwards ("hello, world" t format))
Когда компилятор Lisp видит эту строку кода, он смотрит на первый атом в списке (
backwards
) и замечает, что он называет макрос. Он передает неоцененный список("hello, world" t format)
макросу, который переупорядочивает список(format t "hello, world")
. Результирующий список заменяет выражение макроса, и это то, что будет оцениваться во время выполнения. Окружение Lisp увидит, что его первый atom (format
) является функцией, и оценит его, передав ему остальные аргументы.
В Лиспе решить эту задачу легко (поправьте меня, если я ошибаюсь), потому что код реализован в виде списка ( s-выражения ?).
Теперь взгляните на этот фрагмент OCaml (который не является гомоиконическим):
let print () =
let message = "Hello world" in
print_endline message
;;
Представьте, что вы хотите добавить гомоконичность в OCaml, который использует гораздо более сложный синтаксис по сравнению с Lisp. Как бы Вы это сделали? Должен ли язык иметь особенно простой синтаксис, чтобы достичь гомоконичности?
РЕДАКТИРОВАТЬ : из этой темы я нашел другой способ достижения гомоконичности, отличающийся от Lisp: тот, который реализован на языке io . Это может частично ответить на этот вопрос.
Здесь давайте начнем с простого блока:
Io> plus := block(a, b, a + b) ==> method(a, b, a + b ) Io> plus call(2, 3) ==> 5
Итак, блок работает. Блок плюс добавил два числа.
Теперь давайте сделаем некоторый самоанализ этого маленького парня.
Io> plus argumentNames ==> list("a", "b") Io> plus code ==> block(a, b, a +(b)) Io> plus message name ==> a Io> plus message next ==> +(b) Io> plus message next name ==> +
Горячая святая холодная плесень. Вы можете не только получить названия параметров блока. И не только вы можете получить строку полного исходного кода блока. Вы можете проникнуть в код и просмотреть сообщения внутри. И самое удивительное: это ужасно просто и естественно. Верный для квеста Ио. Зеркало Руби не видит ничего из этого.
Но, воу, воу, эй, не трогай этот диск.
Io> plus message next setName("-") ==> -(b) Io> plus ==> method(a, b, a - b ) Io> plus call(2, 3) ==> -1
Ответы:
Вы можете сделать любой язык homoiconic. По сути, вы делаете это путем «зеркалирования» языка (то есть для любого языкового конструктора вы добавляете соответствующее представление этого конструктора в виде данных, подумайте AST). Вам также необходимо добавить несколько дополнительных операций, таких как цитирование и отмена цитаты. Это более или менее так.
У Lisp это было рано из-за его простого синтаксиса, но семейство языков MetaML W. Taha показало, что это возможно для любого языка.
Весь процесс изложен в Моделировании однородного генеративного метапрограммирования . Более легкое введение в тот же материал здесь .
источник
Компилятор Ocaml написан на самом Ocaml, поэтому, безусловно, есть способ манипулировать AST Ocaml в Ocaml.
Можно было бы представить добавление встроенного типа
ocaml_syntax
к языку и наличиеdefmacro
встроенной функции, которая принимает ввод типа, скажемТеперь то , что это тип из
defmacro
? Ну, это действительно зависит от ввода, как будто дажеf
это функция тождества, тип получающегося фрагмента кода зависит от фрагмента синтаксиса, переданного в.Эта проблема не возникает в lisp, поскольку язык динамически типизирован, и никакой тип не должен быть приписан самому макросу во время компиляции. Одним из решений было бы иметь
что позволит использовать макрос в любом контексте. Но это небезопасно, конечно, это позволило бы
bool
бы использовать вместоstring
, сбой программы во время выполнения.Единственным принципиальным решением в статически типизированном языке будет наличие зависимых типов, в которых тип результата
defmacro
будет зависеть от входных данных. Однако в этот момент все становится довольно сложным, и я бы хотел начать с диссертацию Дэвида Рэймонда Кристиансена.В заключение: наличие сложного синтаксиса не является проблемой, поскольку существует много способов представить синтаксис внутри языка и, возможно, использовать метапрограммирование как
quote
операцию для встраивания «простого» синтаксиса во внутреннийocaml_syntax
.Проблема состоит в том, чтобы сделать это хорошо типизированным, в частности, имея механизм макро-времени выполнения, который не допускает ошибок типа.
Имея время компиляции механизм макросов на языке , как OCaml можно, конечно, смотри , например , , MetaOcaml .
Также возможно полезно: улица Джейн на метапрограммировании в Окамле
источник
В качестве примера рассмотрим F # (на основе OCaml). F # не полностью гомоичен, но поддерживает получение кода функции как AST при определенных обстоятельствах.
В F # ваш
print
будет представлен как,Expr
который печатается как:Чтобы лучше выделить структуру, вот альтернативный способ, как вы могли бы создать то же самое
Expr
:источник
eval(<string>)
функцию? ( Согласно многим ресурсам, наличие функции eval отличается от гомоконичности - это причина, по которой вы сказали, что F # не полностью гомоичен?)print
с[<ReflectedDefinition>]
атрибутом.)