?

Log in

No account? Create an account

Метапрограммирование в Агде и немного философии - Excelsior

Aug. 27th, 2013

09:49 am - Метапрограммирование в Агде и немного философии

Previous Entry Share Next Entry

Comments:

[User Picture]
From:xeno_by
Date:August 27th, 2013 10:37 pm (UTC)
(Link)
Кстати, в Template Haskell все в порядке с гигиеной. Переменные, которые биндятся к глобальным определениям, реифицируются как fully qualified ссылки на эти определеня, а переменные, которые биндятся к локальным определениям, переименовываются (см. страницу 25 презентации https://github.com/scalamacros/scalamacros.github.com/blob/master/paperstalks/2012-09-10-CandidacyExamSlides.pdf?raw=true).

Единственная проблема, что там у всех квазицитат биндинги должны ресолвиться статическим образом, т.е. [| f $p = x + y |] работать не будет.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 27th, 2013 11:34 pm (UTC)
(Link)
Конечно же в TH все в порядке - делать макросистему со статическими гарантиями корректности и без гигиены было бы, как минимум, странно :) Речь шла о формальном определении гигиены. На интуитивном уровне всем понятно, что это за зверь, и реализации гигиены - есть, но вот нормального формально строгого определения - нет. Я выше кинул ссылку на статью по этой теме как раз.

ЗЫ: посмотрел обход гигиены в TH на 19 странице слайдов - это, конечно, тихий ужас. Взять и переложить ответственность на пользователя макроса - архигениальное решение.

Edited at 2013-08-27 11:44 pm (UTC)
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 28th, 2013 05:59 am (UTC)
(Link)
Вы имеете ввиду $(dyn "it") на страничке, на которой справа-снизу написано 26?
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 28th, 2013 11:17 am (UTC)
(Link)
Ага, его.

Ну TH вообще в целом как макросистема на мой взгляд предельно неудобен.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 05:48 am (UTC)
(Link)
А чем $(dyn "it") отличается от (syntax-local-introduce #'it)?
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 09:49 am (UTC)
(Link)
Тем, что $(dyn "it") надо писать при вызове каждый раз пользователю, а (syntax-local-introduce #'it) пишет один раз макрописатель.
(Reply) (Parent) (Thread) (Expand)
[User Picture]
From:xeno_by
Date:August 29th, 2013 05:48 am (UTC)
(Link)
Какие бы вы выделили основные недостатки макросистемы TH?
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 09:12 am (UTC)
(Link)
1. Это даже не про саму макросистему - отсутствие хорошего, качественного АПИ. Пусть даже система не слишком выразительна - но написание макросов очень узкая задача (разбор и генерация АСТ), а раз задача очень узкая - для нее можно написать удобный, специализированный апи/дсл/етц. (все эти pattern language и т.п.) Да, он не будет универсален - но покроет большую часть юзкейсов и кардинально повысит юзабельность. Те 10%, что он не покроет, я уж буду писать руками, не проблема, но не заставляйте меня самому крутить педали в оставшихся 90%.

2. Требование заведомой корректности типов на выходе - сильно ограничивает выразительность, понятное дело

3. Отсутствие локальных макросов, macrogenerated макросов - многие "патерны" использования макросов на них основаны.

4. Отсутствие гомоиконности в конструкциях языка - когда синтаксис конструкций спроектирован сразу так, что его _удобно_ разбирать и генерить - это большой плюс. Но чтобы исключить этот недостаток, необходимо быть лиспом :)

*хороший АПИ из первого пункта может свести к минимуму подобного рода недостаток

5. Вызов макросов (и вот эти $(dyn "it")) туда же. Макрос пишется, чтобы пользователю было _удобно_. Если вы для использования макросов (и даже банального вызова) заставляете писать кучу лишних закорючек - это никак не способствует удобству. Пусть писать макросы даже будет немного сложнее - но для пользователя должно быть все предельно легко, прозрачно и с минимальными затратами. Никаких лишних телодвижений.

6. Отсутствие всяких фич, специально сделанных для макросов - типа тех же syntax parameters, renamers, возможности ассоциирования с символами произвольной статической информации и ее использования

7. Отсутствие поддержки со стороны рантайма - syntax objects, фазы, модульная система. По-хорошему рантайм должен учитывать наличие макросов, в хаскеле макросы - сбоку припека для рантайма.

Edited at 2013-08-29 09:15 am (UTC)
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 27th, 2013 11:58 pm (UTC)
(Link)
Да, еще, на счет примера с Racket:

(require syntax/parse/define)

(define-simple-macro (aif cond:expr then:expr else:expr)
#:with it (datum->syntax #'aif 'it)
(let ([tmp cond]
[it cond])
(if it then else)))

(define-simple-macro (aunless cond:expr then:expr else:expr)
(aif (not cond) then else))


не работает, конечно, зато вот


(define-simple-macro (aif cond:expr then:expr else:expr)
#:with it (syntax-local-introduce #'it)
(let ([tmp cond]
[it cond])
(if it then else)))

(define-simple-macro (aunless cond:expr then:expr else:expr)
(aif (not cond) then else))

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

Edited at 2013-08-28 12:00 am (UTC)
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 28th, 2013 05:58 am (UTC)
(Link)
Я этот сниппет с datum->syntax брал из паперы http://www.schemeworkshop.org/2011/papers/Barzilay2011.pdf. Можете, пожалуйста, объяснить, в чем разница между datum->syntax и syntax-local-introduce?

Также, есть ли где-то гайд на эту тему? Я помню, что, когда разбирался с Ракетом, именно в этом направлении столкнулся с проблемами. Много разной документации, но совершенно непонятно как все вместе укладывается в большую картину.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 28th, 2013 11:14 am (UTC)
(Link)
Для этого надо понимать алгоритм работы гигиены в Racket. С каждым идентификатором проассоциирован набор syntax marks's. Два одноименных идентификатора могут быть связаны друг с другом тогда и только тогда, когда их marks не отличаются, то есть актуальный биндинг идентификатора - последний биндинг, появившийся до расхождения набора marks. Во время каждого раскрытия макроса генерируется своя уникальная mark и применяется рекурсивно к syntax object'у, потом этот object обрабатывается трансформером (собственно, логика макроса) и дальше к результату опять применяется та же mark. При этом если к объекту два раза подряд применяется одна и та же mark, то они взаимоуничтожаются. Выходит, что у идентификаторов, которые пришли макросу на вход, mark остается тот же, а к идентификаторам, сгенеренным макросом, добавляется mark макровызова. На примере, допустим у нас есть такой макрос:

(define-syntax-rule (macro arg) (let ([x 1]) (+ x arg)))

и мы его вызываем:
(let ([x 2]) (macro x))

(дальше включите Dr.Racket, напишите эти две формы и запустите макростеппер, активируйте stepper -> view syntax properies и stepper->extra options->one term at time, теперь все описанное будет зрительно видно - биндинги стрелочками при наведении на идентификатор, наборы марок - цветами, тыкнув на объект можно посмотреть все его свойства в окне справа)

сперва раскрывается само тело определения макроса и устанавливаются биндинги внутри него (и в квазицитате (let ([x 1]) (+ x arg)))). В этой точке уже связан + и let (т.о. фиксируется, что это + и let из стандартной библиотеки), а х остается свободной (это делается в раскрытии 1 фазы, то есть в степере уже будет сделано, т.к. стоит "hide phase > 0").

далее мы раскрываем (let ([x 2]) (macro x)) и устанавливается связывание для х=2 (то же сделано т.к. стоит "hide library syntax", можете поубирать эти hide галочки потом и посомтреть эти этапы более подробно). Потом раскрываем (macro x), сгенерируется марк с каким-то номером, этот марк применится рекурсивно к форме #'(macro x), соответственно, к идентификаторам #'x и #'macro (второе в дальнейшем важно!). Дальше #'x (уже с маркой и связыванием) подставляется на место arg и запускается макрос. Формируется форма (let ([x 1]) (+ x x)) и к ней опять рекурсивно применяется та же марка, результат возвращается из макроса. Т.о. у нас марка стоит на let, +, x в [x 1] и первом х в (+ x x), на втором х в (+ x x) марки нет. let и + были связаны до появления марок и все ок, так что теперь раскрывается сам этот let, х в let и первый х имеют одинаковые наборы марок - и связываются, второй х марок не имеет - по-этому не связывается, так что остается со своим связыванием во внешней let-форме.

Собственно, (syntax-local-introduce #'x) делает ни что иное, как применяет текущую марку к х, т.о. если х введен макросом, то он "выводит" х из-под гигиены (то есть х в итоге окажется без марки вовсе). (syntax->datum #'x 'y) предназначен для того, чтобы передать весь лексический контекст с х на y (и кроме того еще может передавать source location, syntax properties и вообще все свойства, какие есть у syntax object). Почему "не композируется" вариант с syntax->datum? У нас сперва раскрывается макрос aunless, который вешает на aif свою марку, потом начинает раскрываться aif и когда в нем делаем (syntax->datum #'aif 'it) то на it ложится марка с aif, которая там из aunless. А на it в форме макровызова (aunless 1 it it) никаких марок нет - в результате unbound variable.

> Также, есть ли где-то гайд на эту тему?

Только Racket Reference, в разделе syntax model, если не ошибаюсь. Больше не видел.

> Я этот сниппет с datum->syntax брал из паперы

Как я понимаю, везде суют этот datum->syntax, т.к. это классическое схемное решение (syntax-local-introduce в схеме нет, это чисто ракетовская фишка, связанная именно с ракетовской гигиеной, ну то есть конкретно с ее алгоритмом).
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 07:25 am (UTC)
(Link)
>> и когда в нем делаем (syntax->datum #'aif 'it) то на it ложится марка с aif, которая там из aunless
Ооо!! Вот оно! Спасибо, теперь все стало понятно.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 09:28 am (UTC)
(Link)
Причем тут aif - это pattern-variable, то есть это именно aif, переданный в макроформу, а если бы у нас был паттерн (_ cond then else) вместо (aif cond then else), то это был бы тот aif, который само название макроса, тогда бы тоже не работало, но уже по другой причине - на it лежала бы марка от самого aif (т.к. идентификатор, в отличии от случая с pattern-variable, был бы сгенерен внутри макроса, а не получен в аргументе).
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 07:29 am (UTC)
(Link)
>> В этой точке уже связан + и let (т.о. фиксируется, что это + и let из стандартной библиотеки), а х остается свободной
Можно подробнее на тему того, что в квазицитатах (как я понимаю, по сути, правая часть define-syntax-rule является квазицитатой) связывается, а что нет, а именно:
1) Как ракет понимает, что x связывать не надо?
2) Что было бы, если бы macro находился внутри лета, который объявляет свой собственный +? Плюс бы тогда связался или нет?
3) Что было бы, если бы в квазицитату что-то сплайсилось? Не знаю, как это выразить в ракете, поэтому напишу в Скале: q"val x = 1; $splicedTree; +(x, 1)". Здесь splicedTree может перекрыть биндинги как для икса, так и для плюса. Как это учтет ракет?
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 09:24 am (UTC)
(Link)
> (как я понимаю, по сути, правая часть define-syntax-rule является квазицитатой)

Да, define-syntax-rule потом раскрывается в другую макроформу, где правая часть будет под #'

> 1) Как ракет понимает, что x связывать не надо?

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

> 2) Что было бы, если бы macro находился внутри лета, который объявляет свой собственный +? Плюс бы тогда связался или нет?

Тогда бы + связался не с module-level + из стандартной библиотеки, а вот этим самым lexical +.

> Что было бы, если бы в квазицитату что-то сплайсилось?


(define-for-syntax q+ #'+)

(let ([+ 1])
(define-syntax (macro stx)
#`(#,q+ + +))
(macro))

в результате 2
то есть плюс из внешней квазицитаты остается внешним, внутренний - становится внутренним.

> десь splicedTree может перекрыть биндинги как для икса, так и для плюса.

Как он может их перекрыть?

Edited at 2013-08-29 09:30 am (UTC)
(Reply) (Parent) (Thread) (Expand)