?

Log in

No account? Create an account

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

Aug. 27th, 2013

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

Previous Entry Share Next Entry

Comments:

From:Valentin Budaev
Date:August 28th, 2013 11:28 am (UTC)
(Link)
> Кстати, знакомы ли вы с реализацией гигиены в Ракете?

Про алгоритм гигиены я ниже рассказал, ну там надо просто потыкать в экспандере и будет все понятно :)

А вот именно с тем как привязывается к идентификаторам лексический контекст и как с ним работают (в частности, как работают let-формы, partial-expand в internal definitions и т.п.) - это какое-то страшное колдунство. По идее, можно самому руками устанавливать связывания (и реализовывать let-формы низкоуровнево, то есть связывать переменные, не раскрывая макрос в define/let-форму), но в доках описано мало и криво. Я все собирался порыться в исходник и разобраться, что да как - но так руки и не дошли.

> 2) позицию квазицитаты в исходнике.

В racket работают с source object - который, в частности, имеет source location. Вам в любом случае нужно будет рано или поздно этот source location для квазицитат (и подобъектов) устанавливать - иначе будет большая беда с обработкой ошибок в макрах.

Что до контекстов - как я понимаю, там просто во время экспанда, когда видим let-форму, то создаем контекст, который содержит связанные переменные (когда видим define - добавляем идентификатор в контекст модуля), контексты могут друг в друга вкладываться, сами идентификаторы содержат ссылки на контекст, соответственно. По крайней мере, глядя на ихнее АПИ, все должно примерно так быть.

Или вы про сериализацию контекстов? А зачем оно?
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 07:33 am (UTC)
(Link)
Сериализация нужна для поддержки раздельной компиляции. Если я написал квазицитату в отдельном модуле, скомпилировал ее, и прилинковался к ней из макроса в другом модуле, то я бы хотел, чтобы та квазицитата как-то запомнила свой оригинальный лексический контекст.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 08:57 am (UTC)
(Link)
Так смотрите, у вас либо идентификатор module-level (и тогда информации о контексте не нужно - нужен лишь полный квалификатор имени) либо она lexical - но тогда нет смысла использовать квазицитату в другом модуле - там ведь лексический контекст тоже будет другой и это будет гарантированная ошибка (то есть вы получается попытаетесь использовать идентификатор вне контекста, на который он ссылается.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 09:25 am (UTC)
(Link)
Не совсем. Я вот тут пару примеров привел, когда энергичный ресолвинг биндингов приводит к вопросам: http://xeno-by.livejournal.com/85880.html?thread=821624#t821624.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 10:09 am (UTC)
(Link)
Ну в том примере ничего из контекста не вырывается, все же.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 09:25 am (UTC)
(Link)
Ну и плюс, тут есть Скало-специфичная особенность. От лексического контекста зависит implicit resolution, а его заранее точно не посчитаешь, т.к. он работает на типах.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 10:07 am (UTC)
(Link)
Эээ... а как оно зависит от _стороннего_ лексического контекста?
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 29th, 2013 03:54 pm (UTC)
(Link)
object A {
  class HasX { def x = 42 }
  implicit def anythingHasX(anything: Any): HasX = new HasX
  def helper(arg: Tree) = q"$arg.x"
}

object B {
  macro m = A.helper(q"new Object")
}

m
Вызов макроса m раскроется в new Object().x, и хотелось бы, чтобы последующая компиляция экспаншена нашла anythingHasX, объявленный в лексическом скоупе квазицитаты.

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

Пока что самым простым решением кажется сериализация всего исходного файла вместе с позицией квазицитаты, а потом on-demand тайпчек new Object().foo в контексте сериализованного файла в нужной позиции. Тут походу тоже можно использовать марки для определения того, в каком именно контексте тайпчекать.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 04:41 pm (UTC)
(Link)
> Вызов макроса m раскроется в new Object().x, и хотелось бы, чтобы последующая компиляция экспаншена нашла anythingHasX

Я понял. Тут мешается два концепта - модули (единицы, эквивалентные модулям в вашем языке) и лексические контексты. Сразу уточним отличие лексического контекста от модуля, чтобы не было путаницы - обращение к переменной лексического контекста извне контекста невозможно, а вот обращение к переменной модуля извне модуля - возможно.

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

Как я понимаю, семантически проблема решается следующим образом - все модули (в вашем случае - companion-object'ы, что еще может выступать в их качестве?), которые есть в текущей точке (доступны в данном лексическом контексте), объединены в один такой большой общий контекст-неймспейс (соответственно, и все имплициты, грубо говоря внутренность всех этих модулей "сплайсится" во внешний лексический контекст, который эти модули содержит), при этом часть переменных приватна и мы не можем их использовать в обычном коде, но можем - если в них раскрывается макрос.

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

Когда мы в вашем случае используем m, то имеется транзитивная зависимость от модуля A (m тянет B, B тянет А), соответственно, он должен быть как-то подгружен и весь его внутренний контекст должен быть приватно-доступен в точке вызова m. Тогда единственное, что нам нужно - это привязка цитаты к модулю (сорцев уже и не требуется). А сам модуль и так у нас должен быть. То есть получается такой "локальный импорт" - для выдернутой из B квазицитаты локально импортируется внутренность В.

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

ЗЫ: а собственно, можно ведь просто для квазицитаты, которую мы выдернули, создать локальный контекст, поместить ее внутрь и в этом контексте добавить импорт:
m раскроется в
{
import A._

new Object().x
}

только это должно делаться неявно и импортируется модуль, который указан в source квазицитаты

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

Edited at 2013-08-29 05:05 pm (UTC)
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 30th, 2013 10:38 pm (UTC)
(Link)
Прошу прощения за поздний ответ - сегодня утром появилось срочное дело, которое заняло весь день.

Макросы в Скале работают никак. Есть папера про применение макросов: http://scalamacros.org/paperstalks/2013-04-22-LetOurPowersCombine.pdf, но устранение адхочности и подпиливание макросистемы там отмечено как future work, которым я собственно сейчас и занимаюсь.

Идея про импорты и замечание про то, что окружающий модуль != лексический контекст, звучат очень хорошо. Я, правда, не уверен, что все сложится как надо, т.к.: 1) implicit scope включает в себя не только lexical scope, но еще и экзотические вещи вроде содержимого текущего package object, 2) может как-то выстрелить наследование. Надо внимательно посмотреть, чтобы это все не порушилось.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 31st, 2013 01:10 am (UTC)
(Link)
> implicit scope включает в себя не только lexical scope, но еще и экзотические вещи вроде содержимого текущего package object

Это вы о том, что должен даваться доступ не только к содержимому класса, в котором объявлен имплицит, но и к содержимому пакета, в котором этот класс определен (и который, в общем случае, отличается от пакета, в котором вызван макрос)? Ну по идее это решается добавлением еще одного импорта (для самого пакета).

> может как-то выстрелить наследование.

Это да, выстрелить много чего может - потому и нужна семантика макроэкспанда, чтобы все формально строго проследить.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:August 31st, 2013 06:56 am (UTC)
(Link)
О, я понял еще две проблемы: 1) куча импортов может породить конфликты, когда одно и то же имя импортируется несколькими импортами, 2) у вещей, живущих в implicit scope, есть приоритеты, которые стираются, когда мы импортируем все сразу. Но подумать нужно, да.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 31st, 2013 11:28 pm (UTC)
(Link)
> 1) куча импортов может породить конфликты, когда одно и то же имя импортируется несколькими импортами

Вы забыли учесть гигиену. У того, что внутри {}, из-за нее нету доступа к контексту, в котором находится {}, то есть к импортам точки макровызова. Ну а если в точке макроопределения неоднозначность - то ее и надо решать в точке определения, во время написания квазицитаты, путем указания полного квалификатора, например.

Другое дело, если вы хотите, чтобы могли подхватываться имплициты одновременно точки макроопределения и точки макровызова, но тут уже какая-то неконсистентность выходит.
(Reply) (Parent) (Thread) (Expand)
[User Picture]
From:xeno_by
Date:August 29th, 2013 08:25 am (UTC)
(Link)
Еще было бы интересно услышать ваше мнение насчет МП в D: http://xeno-by.livejournal.com/86554.html
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:August 29th, 2013 09:47 am (UTC)
(Link)
Ну, если макросистема хорошая, то все эти "alias, enum, template, и так далее [2, 3]" можно написать в виде макросов. :)
Это часть того, что я подразумевал под АПИ в своем ответе о недостатках TH. То есть если это частый юзкейс - ну выделить в отдельный слой АПИ, пусть люди не парятся с закатом солнца вручную. А если надо чего другого - ну есть другой слой АПИ, либо, если нету - то уже тогда руками можно.
(Reply) (Parent) (Thread)