Як зробити мову гомоніконічною


16

Згідно з цією статтею, наступний рядок коду Lisp друкує "Hello world" на стандартний вихід.

(format t "hello, world")

Lisp, що є гомоніконімічною мовою , може розглядати код як дані таким чином:

А тепер уявіть, що ми написали такий макрос:

(defmacro backwards (expr) (reverse expr))

назад - це назва макросу, який приймає вираз (представлений у вигляді списку) і повертає його назад. Ось знову "Привіт, світ", на цей раз за допомогою макросу:

(backwards ("hello, world" t format))

Коли компілятор Lisp бачить цей рядок коду, він переглядає перший атом у списку ( backwards) і помічає, що він називає макрос. Він передає неоцінений список ("hello, world" t format)макросу, який переставляє список у (format t "hello, world"). Отриманий список замінює вираз макросу, і саме це буде оцінено під час виконання. Середовище Лісп побачить, що його перший атом ( format) є функцією, і оцінить його, передавши йому решту аргументів.

У Lisp досягти цього завдання досить просто (виправте мене, якщо я помиляюся), оскільки код реалізований як список ( s-вирази ?).

Тепер погляньте на цей фрагмент OCaml (який не є гомоніконічною мовою):

let print () =
    let message = "Hello world" in
    print_endline message
;;

Уявіть, що ви хочете додати гомонікості OCaml, який використовує набагато складніший синтаксис порівняно з Lisp. Як би ти це зробив? Чи має мова володіти особливо легким синтаксисом для досягнення гомонікості?

EDIT : з цієї теми я знайшов інший спосіб досягти гомоконічності, який відрізняється від 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

1
Ви можете поглянути, як Скала робила свої макроси
Бергі

1
@Bergi Scala по-новому підходить до макросів: scala.meta .
Мартін Бергер

Я завжди хоч гомоконічність завищена. На будь-якій достатньо потужній мові ви завжди можете визначити структуру дерева, яка відображає структуру самої мови, а корисні функції можна записати для перекладу на та з вихідної мови (та / або складеної форми) у міру необхідності. Так, у LISPs це трохи простіше, але враховуючи, що (a) переважна більшість робіт з програмування не має бути метапрограмуванням та (b) LISP принесла в жертву ясність мови, щоб зробити це можливим, я не думаю, що компроміс цього не вартий.
Periata Breatta

@PeriataBreatta Ви маєте рацію, але ключовою перевагою MP є те, що MP дозволяє абстрагуватися без пені . Таким чином, МП вирішує напругу між абстракцією та виконанням, хоча ціною зростаючої складності мови. Чи варто того? Я б сказав, що той факт, що всі основні менеджери з оплати праці мають розширення МП, вказує на те, що багато працюючих програмістів вважають корисними пропозиції МП.
Мартін Бергер

Відповіді:


10

Ви можете зробити будь-яку мову гомоніконічною. По суті, ви робите це шляхом "дзеркального відображення" мови (тобто для будь-якого конструктора мови ви додаєте відповідне представлення цього конструктора як даних, подумайте AST). Вам також потрібно додати пару додаткових операцій, таких як цитування та відмітка даних. Це більш-менш це.

У Lisp це було рано через його легкий синтаксис, але сімейство мов В. Таха MetaML показало, що це можливо зробити для будь-якої мови.

Весь процес окреслений в Моделюванні однорідного генеративного метапрограмування . Більш легкий вступ до того ж матеріалу тут .


1
Виправте мене, якщо я помиляюся. "дзеркальне відображення" пов'язане з другою частиною питання (гомоконічність в io lang), правда?
включно

@Ignus Я не впевнений, що я повністю розумію ваше зауваження. Мета гомоконічності - дозволити обробляти код як дані. Це означає, що будь-яка форма коду повинна мати представлення як дані. Існує кілька способів зробити це (наприклад, квазіцитати ASTs, використовуючи типи для відрізнення коду від даних, як це зроблено методом модульної постановки легкої ваги), але всі вимагають подвоєння / дзеркального відображення синтаксису мови в якійсь формі.
Мартін Бергер

Я припускаю, що @Ignus виграв би від перегляду MetaOCaml? Чи означає "бути гомоніконічним" просто тоді, коли він може бути цитованим? Я припускаю, що багатоступеневі мови, такі як MetaML та MetaOCaml, йдуть далі?
Стівен Шоу

1
@StevenShaw MetaOCaml дуже цікавий, особливо новий BER MetaOCaml Олега . Однак він дещо обмежений тим, що він виконує лише метапрограмування під час виконання, і представляє код лише за допомогою квазіцитат, які не є настільки виразними, як AST.
Мартін Бергер

7

Компілятор Ocaml написаний в самому Ocaml, тому, безумовно , є спосіб маніпулювати ASTs Ocaml в Ocaml.

Можна уявити додавання вбудованого типу ocaml_syntaxдо мови та defmacroвбудовану функцію, яка приймає введення типу, скажімо

f : ocaml_syntax -> ocaml_syntax

Тепер то , що це тип з defmacro? Добре, що насправді залежить від введення даних, так як навіть якщо fфункція ідентичності, тип отриманого фрагмента коду залежить від переданого в ньому фрагмента синтаксису.

Ця проблема не виникає у простому режимі, оскільки мова динамічно набирається, і жоден тип не повинен бути приписаний макросу під час компіляції. Одне рішення було б мати

defmacro : (ocaml_syntax -> ocaml_syntax) -> 'a

що дозволило б використовувати макрос у будь-якому контексті. Але це небезпечно, звичайно, це дозволить використовувати a boolзамість а string, збій програми під час виконання.

Єдиним принциповим рішенням в мові статичного типу буде мати залежні типи, в яких тип результату defmacroбуде залежати від введення. На сьогодні цей момент стає досить складним, і я б почав із того, щоб вказати на приємну дисертацію Девіда Реймонда Крістіансена.

На закінчення: складний синтаксис не є проблемою, оскільки існує багато способів представити синтаксис всередині мови та, можливо, використовувати метапрограмування як quoteоперацію для вбудовування "простого" синтаксису у внутрішнє ocaml_syntax.

Проблема полягає в тому, щоб це було добре набрано, зокрема, макро механізм запуску, який не допускає помилок типу.

Маючи час компіляції механізм макросів на мові , як OCaml можна, звичайно, дивись , наприклад , MetaOcaml .

Також можливо корисна: Джейн-стріт на метапрограмуванні в Окамлі


2
MetaOCaml має метапрограмування під час виконання, а не мета-програмування у компіляції. Також система набору тексту MetaOCaml не має залежних типів. (Також було виявлено, що MetaOCaml є невиразним типом!) Шаблон Haskell має цікавий проміжний підхід: кожен етап є безпечним для типу, але при вступі на новий етап потрібно повторно перевірити тип. На моєму досвіді це дуже добре працює, і ви не втрачаєте переваг безпеки типу на останньому етапі (час виконання).
Мартін Бергер

@cody можливе метапрограмування в OCaml також і з точками розширення , правда?
включно

@Ignus Боюся, я не знаю багато про точки розширення, хоча я посилаюся на це у посиланні на блог Джейн Стріт.
коді

1
Мій компілятор C написаний на C, але це не означає, що ви можете маніпулювати AST в C ...
BlueRaja - Danny Pflughoeft

2
@immibis: Очевидно, але якщо це саме він мав на увазі, то це твердження є однозначно і не пов'язане з питанням ...
BlueRaja - Danny Pflughoeft

1

Як приклад розглянемо F # (на основі OCaml). F # не є повністю гомонічним, але підтримує отримання коду функції як AST за певних обставин.

У F # ваш файл printбуде представлений як Exprдрук:

Let (message, Value ("Hello world"), Call (None, print_endline, [message]))

Щоб краще виділити структуру, ось альтернативний спосіб, як можна створити ту саму Expr:

let messageVar = Var("message", typeof<string>)
let expr = Expr.Let(messageVar,
                    Expr.Value("Hello world"),
                    Expr.Call(print_endline_method, [Expr.Var(messageVar)]))

Я цього не зрозумів. Ви маєте на увазі, що F # дозволяє "скласти" AST виразу і потім виконати його? Якщо так, то яка різниця між мовами, які дозволяють використовувати цю eval(<string>)функцію? ( Згідно з багатьма ресурсами, функція eval відрізняється від гомонікості - це причина, чому ви сказали, що F # не є повністю гомоніконічним?)
включити

@Ignus Ви можете створити AST самостійно, або можете дозволити компілятору це зробити. Гомоїконічність "дозволяє отримати доступ до всіх кодів у мові та перетворити їх як дані" . У F # ви можете отримати доступ до деякого коду як даних. (Наприклад, вам потрібно знаком printз [<ReflectedDefinition>]атрибутом.)
svick
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.