?

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 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)
[User Picture]
From:xeno_by
Date:September 1st, 2013 06:32 am (UTC)
(Link)
Нет, подхватывать оба класса имплиситов я бы не хотел. Это действительно неконсистентно.

Про импорты я имел ввиду несколько другое. В Скале и у имплиситов, и у импортов есть приоритеты. Например, если я напишу "import A._; { import B._; ... }", то внутри многоточий импорты из B будут более приоритетны, чем импорты из A. Соответственно, есть шанс, что явное задание импортов сломает приоритеты. Впрочем, наверное, можно исхитриться с вложенностью, чтобы эмулировать нужные приоритеты, но тогда программисту, читающему принтауты квазицитат, может стать неудобно.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:September 1st, 2013 09:54 pm (UTC)
(Link)
> Например, если я напишу "import A._; { import B._; ... }"

Всмысле в точке макроопределения?

> Впрочем, наверное, можно исхитриться с вложенностью, чтобы эмулировать нужные приоритеты, но тогда программисту, читающему принтауты квазицитат, может стать неудобно.

Я не имел ввиду, что надо _реально_ трансформировать квазицитаты в {} с импортами, я говорил о семантике (чтобы можно было reasoning вести), а реализовать можно уже руками в недрах макросистемы. Это же вполне обычный способ - описываем фичу де-юре как некий сахар над ядром, а реализуем уже более эффективно/удобно, главное, чтобы семантически реализация была эквивалентна сахару.

То есть если мы можем обернуть квазицитату в {} с импортами, нам ведь в принципе ничего не мешает воспользоваться апи компилятора и "руками" указать ему: "считай, что здесь были вот эти импорты, хотя их и нет по факту". Или мешает?

ЗЫ: а source location в любом случае для отладки тянуть полезно в представлении АСТ, без него нормальные ошибки из макроса не прокинешь.
(Reply) (Parent) (Thread) (Expand)
[User Picture]
From:xeno_by
Date:September 2nd, 2013 11:59 am (UTC)
(Link)
Понятно. Да, можно оставить такие вещи деталью реализации. Думаю, что компилятору это можно объяснить.

Позишены (так у нас называются source locations) тянуть уже можно прямо сейчас, так что по крайней мере здесь у нас все в порядке :)
(Reply) (Parent) (Thread) (Expand)