?

Log in

No account? Create an account

Let our powers combine! - Excelsior — LiveJournal

Apr. 23rd, 2013

09:46 am - Let our powers combine!

Previous Entry Share Next Entry

Только что засабмитил паперу на воркшоп Scala 2013: http://scalamacros.org/news/2013/04/22/let-our-powers-combine.html.



Работа состоит из двух содержательных частей, не считая введения, быстрого знакомства с макросами и заключения. В первой части (Section 3) рассказывается про разные виды макросов (def macros, type macros, macro annotations), необходимость в которых проистекает из-за наличия в Скале синтаксиса, а также про их разновидности, возникающие на пересечении с тами или иными языковыми фичами. Во второй части (Section 4) рассматриваются сценарии использования макросов из первой части в сравнении с альтернативными подходами из других фреймворков и языков:
* Виртуализация языка
* Тайп провайдеры
* Автогенерация инстансов тайпклассов
* Упрощение программирования на уровне типов
* Интеграция с внешними доменно-специфическими языками
* Реализация новых языковых фич

Уважаемым читателям моего журнала папера может быть интересна в контексте обсуждений на следующие темы:
1) Вот я юзаю Скалу, и все ок. Зачем мне могут понадобиться макросы? (см. раздел 4. примеры там несколько ориентированы на академию, но большинство из них все равно взято из практики)
2) Зачем в Скале макросы, если уже есть scala-virtualized и LMS? (см. раздел 4.1)
3) В Скале слишком много фич, поэтому метапрограммирование там неоправданно усложнено (см. всю паперу на тему сценариев использования макросов, которые стали возможны благодаря тем или иным фичам Скалы)
4) Скале нужно метапрограммирование потому, что она недостаточно хорошо продумана (см. в принципе весь раздел 4, но в особенности 4.3 и 4.4, еще можно нагуглить технический отчет по Yin-Yang из списка литературы и почитать его)

В будущих работах я планирую рассказать про устройство нашей макросистемы (в этой папере я специально не вдавался в детали), а также обсудить трейдоффы в дизайне (сильно типизированные деревья или нет, раздельная компиляция или нет, какие API компилятора показывать наружу, а какие нет и так далее в таком духе).

Tags: ,

Comments:

[User Picture]
From:sorhed
Date:April 23rd, 2013 08:00 am (UTC)
(Link)
Я тебя уже спрашивал, но, видимо, не пришло. Если я хочу на Scala Days поехать, мне надо платить за регистрацию? :)
(Reply) (Thread)
[User Picture]
From:xeno_by
Date:April 23rd, 2013 08:11 am (UTC)
(Link)
Я ответил на почту, но видимо ее захавал гугл. Насколько мне известно, без регистрации на конфу едут только докладчики. Но в принципе еще не поздно подготовить доклад на европейский ScalaDays, который планируется осенью =)
(Reply) (Parent) (Thread)
[User Picture]
From:henu3detb
Date:April 23rd, 2013 08:40 am (UTC)
(Link)
На экране текст, разбитый на колонки, читать мягко говоря не очень.
(Reply) (Thread)
From:rssh
Date:April 23rd, 2013 11:56 am (UTC)
(Link)
Да, про макросы из Dynamic я не знал ;))
(Reply) (Thread)
From:ex_juan_gan
Date:April 23rd, 2013 06:58 pm (UTC)
(Link)
Спасибо.
Да, побольше бы про макросы.
Не всё так уж оценил, где-то слишком много бойлерплейта... ну посмотрим, привыкнем. Интерполяцию уже усвоил как родную;
Спасибо за линк на идиомы, замечательный проект.
(Reply) (Thread)
[User Picture]
From:xeno_by
Date:April 23rd, 2013 07:07 pm (UTC)
(Link)
С бойлерплейтом, надеюсь, скоро станет попроще. Вот квазицитаты появились (http://docs.scala-lang.org/overviews/macros/quasiquotes.html), есть еще кое-что в процессе. А что конкретно вам запомнилось из раздражающего?
(Reply) (Parent) (Thread)
From:ex_juan_gan
Date:April 23rd, 2013 07:19 pm (UTC)
(Link)
Ну хм, вот так вот писать совсем не хочется:

def printf(format: String, params: Any*): Unit = macro impl
def impl(c: Context)(format: c.Expr[String],
params: c.Expr[Any]*): c.Expr[Unit] = ...
printf("hello %s", "world")

А, и ещё, там ниже употребляется идентификатор meth - это, по нашим местным обычаям, слишком свирепый идентификатор. Я понимаю, швейцарская чистота души, но у нас meth имеет только одно значение.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 23rd, 2013 09:10 pm (UTC)
(Link)
Поправил, спасибо.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 23rd, 2013 09:11 pm (UTC)
(Link)
Хочется писать вот так: "def printf(format: String, params: Any*) = macro { ... }"?
(Reply) (Parent) (Thread)
From:ex_juan_gan
Date:April 23rd, 2013 10:09 pm (UTC)
(Link)
:) Ну да.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 23rd, 2013 10:21 pm (UTC)
(Link)
Тогда получается фигня со скоупингом из-за того, что макросы можно объявлять где угодно (т.е. например как локальные функции).

Соответственно, реализации макросов, записанных в предлагаемом виде, будут видеть переменные из лексического скоупа определения макроса. А это ведет к непонятным спецэффектам.

Например, что означает { val x = someTree; def foo = macro x; foo }? Откуда в момент исполнения макроса возьмется значение для икса? Во время раскрытия макроса этот икс еще даже не скомпилирован.

В Немерле и Хаскелле такой проблемы нет, так как макросы там могут быть объявлены только в топ-левеле. В Скале же вообще нет концепции топ-левел функции.

(Reply) (Parent) (Thread)
From:ex_juan_gan
Date:April 23rd, 2013 10:38 pm (UTC)
(Link)
Ну да, ну да.
Надо подумать. Меня интересуют исключительно вопросы стиля. :)
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 24th, 2013 05:01 am (UTC)
(Link)
> Соответственно, реализации макросов, записанных в предлагаемом виде, будут видеть переменные из лексического скоупа определения макроса.

Так это же определение гигиены. Во всех схемах так, и ни к чему плохому это не приводит. Или в чем тут проблема?

> Например, что означает { val x = someTree; def foo = macro x; foo }? Откуда в момент исполнения макроса возьмется значение для икса? Во время раскрытия макроса этот икс еще даже не скомпилирован.

phase separation?

(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 24th, 2013 06:12 am (UTC)
(Link)
1)
class Foo { 
  private val x = 2
  def foo = macro { q"x" } // дерево, ссылающееся на x
}


Что делать, если этот макрос юзается вне класса Foo?
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 24th, 2013 09:31 am (UTC)
(Link)
Вызов foo раскрывается в "х", я верно понял? То есть для выполнения тела макроса нам значение х не нужно? Ну тогда очевидно:

scala> 1+x
:8: error: not found: value x
1+x
^

scala>

а какие еще могут быть варианты?
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 24th, 2013 09:58 am (UTC)
(Link)
Если мы позволяем смешивать скоупы, то хотелось бы, чтобы это делалось красиво, т.е. чтобы x, в который раскрывается макрос, ссылался на филд x, объявленный в классе (так, например, делается в Template Haskell).

Но тогда возникает проблема с видимостью. Если мы раскрываемся внутри класса, то x виден и все работает. Иначе все внезапно перестает работать.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 24th, 2013 11:14 am (UTC)
(Link)
> Если мы позволяем смешивать скоупы, то хотелось бы, чтобы это делалось красиво, т.е. чтобы x, в который раскрывается макрос, ссылался на филд x, объявленный в классе

Ну так он и ссылается на филд х, объявленный в классе, но в скопе раскрытия макроса этого идентификатора нет (и быть не может, т.к. он приватный) - ошибка. Вот если мы каким-то образом сумеем в скоп раскрытия импортировать лексический контекст Foo и связать внутри него х - тогда все будет хорошо. Ну в схеме, по крайней мере, это так делается, и никаких проблем не возникает.

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

Edited at 2013-04-24 11:17 am (UTC)
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 24th, 2013 05:07 pm (UTC)
(Link)
Звучит логично, но получается неконсистентность, которая меня немного нервирует. Как бы есть лексический скоуп, но как бы его и нет. Причем правила скоупинга разные для тела макроса и для квазицитат в теле макроса. Кстати, эта тема уже поднималась в http://xeno-by.livejournal.com/70984.html.

(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 25th, 2013 02:25 am (UTC)
(Link)
> но получается неконсистентность

В чем, собственно?

> Как бы есть лексический скоуп, но как бы его и нет.

Да он всегда есть, просто в разных точках программы он разный. Если у вас есть какой-нибудь лексический блок (let ([x 1]) x), здесь х ссылается на х, объявленный в соответствующем блоке - и в идентификаторе сохраняется эта информация. Теперь вы выдираете х и вставляете его вне этого лексического блока - ну естественно это будет ошибкой, идентификатор пытается выяснить, с чем он связан, ищет в текущем скоупе соответствующий х - и не находит.

> Причем правила скоупинга разные для тела макроса и для квазицитат в теле макроса.

Почему?

> Кстати, эта тема уже поднималась в http://xeno-by.livejournal.com/70984.html.

Тут как раз проблема из-за отсутствия схемовских фаз (ну или общелиспового eval-when), когда непонятно, что, когда исполняется и какая должна быть область видимости (раз уж она связана с семантикой исполнения). Чем, опять же, плох схемовский вариант, когда код старшей фазы не видит кода младшей фазы, то есть вызов окружающего кода изнутри макроса запрещен?

Вопрос же относительно доступа к приватному полю из квазицитаты - это вопрос совершенно ортогональный. Здесь, во-первых, почему скоп класса должен чем-то отличаться от скопа функции (по сути приватное поле эквивалентно локальной переменной ф-и)? Во-вторых, если разрешить вызов макросов, раскрывающихся в приватные поля, вам придется еще велосипедить аналог syntax-arms/code inspectos из Racket, иначе вы нарушаете инкапсуляцию - можно будет выдернуть это приватное поле из дерева экспанда макроса и спокойно им пользоваться из внешнего кода, забив на то, что оно приватное.
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 25th, 2013 11:48 am (UTC)
(Link)
Что такое окружающий код? Только кодяра, определенная в текущем compilation unit или заодно еще и рядом лежащие библиотеки?
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 25th, 2013 12:18 pm (UTC)
(Link)
Библиотеки в том числе, естественно. Ведь код в библиотеке и код в текущем юните - формально ничем не отличаются, почему же макрос должен их воспринимать по-разному?
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 25th, 2013 12:21 pm (UTC)
(Link)
А как тогда, например, парсить XML в макросах? Либа для парсинга это же внешний код.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 25th, 2013 12:32 pm (UTC)
(Link)
А вот я даже не знаю. Мне вообще в этом смысле семантика макросов на базе jvm кажется неясной. Ну то есть в схеме есть фазы (и библиотеку можно загружать в каждую отдельно), в общелиспе есть глобальное состояние лисп-машины (и подгрузка библиотеки = изменение этого состояния, т.к. подгружается в топ-левел), а в случае jvm какой-то четкой картинки того, как это вообще работает, не складывается. import же фактически ничего не делает.

Edited at 2013-04-25 12:34 pm (UTC)
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 25th, 2013 11:48 am (UTC)
(Link)
А про лексический скоуп ты меня почти убедил. Может, оно так и надо. Нужно будет на днях обдумать в деталях.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 25th, 2013 12:25 pm (UTC)
(Link)
Я как понимаю, проблема здесь основная в том, что ни фаз, ни eval-when, ни других подобных вещей не сделаешь - они должны иметь поддержку со стороны рантайма, а рантайм их не поддерживает, ведь это рантайм jvm. Приходится использовать для разделения то, что есть - статические классы, хотя они для этого не предназначены. В результате получается все сложно и замкнуто на семантику jvm.

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

ЗЫ: а что, кстати, будет, если определить макрос в реализации другого макроса? Как это вообще разрешается? И что будет если макрос вызывается сам из себя (или в реализации макроса вызывается другой макрос текущего юнита)?

Edited at 2013-04-25 12:28 pm (UTC)
(Reply) (Parent) (Thread)
[User Picture]
From:xeno_by
Date:April 24th, 2013 06:14 am (UTC)
(Link)
2) Phase separation это следующий шаг. Первый шаг это разобраться в том, какая тут вообще должна быть семантика, когда половина кода, который видит и может потенциально вызывать макрос, еще не скомпилирована.
(Reply) (Parent) (Thread)
From:Valentin Budaev
Date:April 24th, 2013 09:24 am (UTC)
(Link)
Ну в схеме все просто - макрос окружающий код младшей фазы не видит и вызывать, соответственно, не может :)

В CL сложнее, т.к. уже будут танцы с бубном вокруг eval-when и конкретной семантики исполнения формы.
(Reply) (Parent) (Thread)