Excelsior - так ли нужна для счастья мощь макросов?
Feb. 15th, 2013
03:32 pm - так ли нужна для счастья мощь макросов?
Обязательна ли для счастья вся мощь манипуляции абстрактными синтаксическими деревьями или же достаточно менее мощного, но более формализованного решения? Эту тему мы на прошлой неделе обсуждали в гостях в
udpn: http://udpn.livejournal.com/94146.html
Скажу сразу, мне теоретически нравится идея построения красивого базиса для метапрограммирования, но на текущий момент варианты, которые мы попробовали в Скале, не кажутся мне рабочими. Пока что я всего лишь наблюдаю за тем, как народ использует макросы и дописываю то, что очень требуют. Систематизация будет чуть позже, пока что все силы отнимает имплементация.
Как народ юзает макросы? По этому поводу я пару месяцев назад писал саммари: http://scalamacros.org/news/2012/11/0
Какие варианты пробовали, почему не понравились? По-хорошему, здесь надо написать паперу, а не пост. Сейчас я попробую очень вкратце выразить свои мысли. Я заранее извиняюсь, что кратко, но буду рад ответить на любые каменты в пределах своих знаний.
Классический способ метапрограммирования в Скале - вычисления на типах и имплисит параметрах. Я здесь совсем не специалист, но люди достигают потрясающих результатов. Управляют тайп инференсом, разбирают типы на запчасти, запрещают или разрешают вызовы методов, и т.д. Тут может больше рассказать уважаемый
ivan_gandhi. К сожалению, временами получается довольно тяжеловесная жесть: https://github.com/scalaz/scalaz/blob/5
То, что мы пробовали вначале как основу для макросов, очень похоже на MacroML. Строго типизированные квазицитаты по имени reify (т.е. никаких тебе игр с биндингами), нет поддержки деструктурирования. В теории это, может, и выглядит хорошо (по разговору с Тахой - Walid Taha - я так понял, что он макросы в неконтролируемом смысле вообще не уважает), но на практике пока что народ не нашел способов юзать reify хоть отдаленно эффективно, т.к. почти всегда (см. use cases выше) нужны нетипизируемые квазицитаты (которые по отдельности смысла не имеют, а, будучи слепленными вместе, дают вменяемый результат).
К сожалению, это пока что все. Много времени уходит на реализацию, да и сейчас после двух недель багфиксов для 2.10.1, я буду скорее всего садиться за починку дурной, но очень важной детали реализации. Пока что мой план заключается в том, чтобы добиться легкости Немерле в реалиях нашего компилятора, а потом уже браться за новизну.
IMVHO, конечно. Некоторый народ в лепёшку разобьётся, но потратит день, чтобы в три строчки записать то, что за пятнадцать минут можно записать в десяти.
Я как раз недавно думал написать макрос для c++ вставок :)
Чтобы макрос добавлял JNI boilerplate, сохранял файл, вызывал компилятор/линкер и подгружал DLL.
Я это уже сколько лет твержу...
http://akuklev.livejournal.com/1032
http://sorhed.livejournal.com/568495.ht
http://udpn.livejournal.com/94146.html
вроде бы есть некоторая граница, дальше которой отрицать необходимость наличия средств манипуляции трудно
именно, похоже, что обязательным должно быть:
1. удобный экспорт синтаксического дерева, типов, аннотаций и т.п. в какой-то формат
2. удобный импорт снаружи всего этого или их частей
т.е. вопрос обработки (т.е. экспортировали, преобразовали, импортировали обратно в скалу) уже на усмотрение конкретной группы программистов; причем язык и способ обработки может быть совершенно независим от скалы (типичная кстати юниксовая заповедь -- предпочитайте форматы данных, а не апи)
как частный случай п.1 получаем type provider-ы -- они однозначно нужны (скажем, генерация типов данных скалы по sql); п.2 -- наоборот, экспорт типов данных скалы в описание таблиц sql
в результате прилично нейтрализовано возражение "ваши макросы дают возможность обмануть себя, что вы пишите на скале, хотя на самом деле вы ее генерируете" -- по крайней мере, ответственность за подобный самообман возложена на конкретных программистов, а не создателей языка :-)
еще: получается такой вот вынос вычислительного контекста времени компиляции в постороние тулзы; хотя я уверен, что вычислительный контекст времени компиляции нужен, но какой именно можно разрешить -- это вопрос
чисто практический вопрос по ходу дела:
мне лично местами не нравится синтаксис скалы, и я хотел бы погенерить ее -- те средства, что даются в scala.reflect.api.Universe, достаточны для этого?
Edited at 2013-02-16 12:26 am (UTC)
Одна из проблем наших текущих макросов заключается в том, что представление кода очень сильно завязано на внутренности компилятора. Например, такую простую вещь как взять дерево из какого-то контекста и пересадить его в другой контекст не всегда возможно сделать, т.к. пересаженные деревья потащат за собой символы, которые потом крэшнут компилятор из-за несоответствия контекстов.
Но символы в деревьях нужны, т.к. с помощью них связываются определения идентификаторов и их использования. Поэтому один из вариантов фикса - разработать такое представление, к которому не привязаны невидимыми нитями кишки компилятора. Например, тупые кейс классы.
> программируемый вывод типов (docs.scala-lang.org/overviews/macros/in
сама идея вывода типов исходя не из академических соображений, а из конкретики DSL безусловно здравая (хотя и тут необходимо разобраться с опасениями)
но в данном конкретном случае
я пастернака не читал, но осуждаюхотя я и не разобрался, но полагаю, что раз компилятор заносит в вывод Nothing, то где-то надо ему указать правильную ко/контра-вариантность, чтобы он трудился в противоположном от Nothing направленииесли я не прав, прошу подробно разъяснить, почему это так
Edited at 2013-02-16 12:50 am (UTC)
/me заранее принимает корвалольчик
то, что я видел на имплицитах (опять до конца не разобравшись) мне очень напоминает извращения с шаблонами на с++; тут специализированный вычислительный контекст времени компиляции был бы более уместен
> Строго типизированные квазицитаты по имени reify (т.е. никаких тебе игр с биндингами)
биндинг это ввод нового имени в локальный контекст? с одной стороны, да, биндинги нужны; с другой -- ну как обычно (можно получить плюсовые шаблоны)
> т.к. почти всегда (см. use cases выше) нужны нетипизируемые квазицитаты (которые по отдельности смысла не имеют, а, будучи слепленными вместе, дают вменяемый результат)
вот мы и подошли к самому интересному -- можно ссылочки поконкретнее (не просто "см. выше")?
там "квазицитаты по отдельности смысла не имеют" из-за того, что квазицитатам требуются имена неких общих (разделяемых) сущностей (переменных) для коммуникации, или еще по какой другой причине?
> нет поддержки деструктурирования
эээ... ммм... а почему его там нет? от него же вроде ну совсем никаких пакостей ожидать нельзя, сплошной safe/typesafe? (я так понял, что деструктурирование = паттерн_матчинг_в_контексте_макросов)
> Много времени уходит на реализацию
зато ты разбираешься во внутренностях компилятора (чего, кстати, и мне бы хотелось, но не хотелось бы тратить на это время, гы-гы)
Edited at 2013-02-16 01:14 am (UTC)
На вопросы про биндинги и причину нужды в квазицитатах это ты в точку написал. Все так и есть.
А как типобезопасным образом деструктурировать AST? Вот скажем, у нас есть c.Expr[Int], то есть AST, имеющее тип Int. Как сопоставить какие-то типы его запчастям? Это AST может быть вызовом функции от неизвестного числа аргументов, может быть селектом какого-то филда у какого-то класса и так далее.
дело в том, что "вся мощь" заранее непредсказуема; т.е. если скажем на дженериках sort(my_container) гарантированно скомпилится при условии правильной сигнатуры sort, то с шаблонами с++, дающими почти "всю мощь", это не так -- сигнатура sort может быть подходящая, но где-то в *кишках* sort возникнет облом, *не описанный в сигнатуре* sort
это плохо, от этого пытаются избавиться (например, придумали концепты -- они позволяют уточнить сигнатуру sort), и это потенциально плохо и в макросах
для макросов возможны разные точки зрения, скажем:
1. пока что просто пишем макросы, вопросы их верификации решаем потом или спихиваем на программистов
2. выбираем ограниченный язык написания макросов, который дает сразу верифицированные (в каком-то смысле) макросы
я как-то больше склонен к 2 (и поэтому пытаюсь понять, че там напридумывали sorhed, akuklev или в lms для скалы)
видимо возможны и какие-то промежуточные варианты
Edited at 2013-02-16 01:34 am (UTC)
Насчёт меня - You are kidding me. Я только учусь ещё этому делу. Т.к. сейчас происходит революция в программировании, то мы все примерно в одинаковом положении.
Со скалазями какая-то фигня. Я на днях выкинул из проекта. Проще самому написать маленькую вещь по мотивам скалазей, чем тащить весь этот ужас.
Если уж предполагается достаточно частое использование макросов в языке, то незачем городить такие сложности, как язык Скала.
Сделать базу языка из просто-типа-лямбды с индуктивными типами, рекурсией и ленивыми вычислениями в compile time.
И работать дальше над хорошей библиотекой макросов, реализующих всякий полиморфизм, подтипизацию и остальное.
Еще как показывает опыт, есть определенный смысл работать над интеграцией макросов в уже существующий тайпчекер потому что получаются неожиданные пересечения традиционных фич с компайл-тайм вычислениями (например, раздел 4.3 в http://scalamacros.org/news/2013/04/2
Вроде,