February 15th, 2013

glider

макросы для внутреннего использования

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

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

***

Пока что единственный уже внедренный пример - генерация реифицированных типов при помощи макроса, но есть и несколько идей на будущее. Начнем с того, что уже реализовано.

Генерация реифицированных типов работает следующим образом. Если кому-то нужно для тайп-параметра T заполучить реифицированный тип (т.е. чтобы каждый полиморфный вызов сохранял тайп-аргумент T), то он пишет: def foo[T](параметры блаблабла)(implicit t: TypeTag[T]) = [тело функции бла бла]. У этой записи есть синтаксический сахар, т.е. она обычно очень компактная, но сейчас не об этом. В итоге, если программист вызывает foo[Int](...), то компилятор сгенерирует TypeTag[Int]. Если вызывается foo[List[T] forSome { type T <: C }](...) или еще какая-нибудь жесть, компилятор все равно сгенерирует точное представление тайп-аргумента. Наконец, если функция foo расположена внутри метода bar[T] с тайптегом для T и вызывается как foo[T](...), то тайптег из bar протечет в вызов foo. Могу потом подробнее рассказать.

Раньше в Implicits.scala, слое компилятора, который отвечает за имплисит серч, был хардкод типа "if (tp.typeSymbol == definitions.TypeTagClass) synthesizeTypeTag". А теперь там вообще нифига нет. Просто в стандартном prelude у нас есть макрос "implicit def materializeTypeTag[T]: TypeTag[T]" (на самом деле, немного не так, но это технические детали). В итоге: убрали хардкод из тайпчекера (т.е. упростили и сделали красивее) + мне (не как аппликейшен девелоперу, а как мейнтейнеру компилятора) стало гораздо легче менять реализацию materialize, т.к. она теперь связана с API компилятора через интерфейс macro API, а не в стиле "можем использовать все, до чего импорты дотянутся".

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

Также отличной, но мой взгляд, является идея парсер макросов. Вынос левых кусочков синтаксиса, вроде list comprehensions, в отдельные модули представляется мне полезным. Но это вещь, над которой еще надо будет поэкспериментировать.

О. Еще. При помощи макросов можно одновременно и упростить, и обобщить deriving (для тебя это, вероятно, не новость, но для полноты можно упомянуть). Кое-что на эту тему было видно вот тут: http://docs.scala-lang.org/overviews/macros/inference.html, но есть и другой аспект - при помощи макросов можно генерить бойлерплейт-методы в классах. Даже при текущих убогих технологиях народ уже что-то фигачит: https://github.com/dicarlo2/ScalaEquals, а дальше можно будет просто поставить аннотацию на класс, и она сама все сделает.
glider

так ли нужна для счастья мощь макросов?

Обязательна ли для счастья вся мощь манипуляции абстрактными синтаксическими деревьями или же достаточно менее мощного, но более формализованного решения? Эту тему мы на прошлой неделе обсуждали в гостях в udpn: http://udpn.livejournal.com/94146.html и фрагмент этого обсуждения я бы хотел вынести в этот пост.

Скажу сразу, мне теоретически нравится идея построения красивого базиса для метапрограммирования, но на текущий момент варианты, которые мы попробовали в Скале, не кажутся мне рабочими. Пока что я всего лишь наблюдаю за тем, как народ использует макросы и дописываю то, что очень требуют. Систематизация будет чуть позже, пока что все силы отнимает имплементация.

Как народ юзает макросы? По этому поводу я пару месяцев назад писал саммари: http://scalamacros.org/news/2012/11/05/status-update.html. С того времени добавилось еще вот что интересное: 1) тайп провайдеры (http://docs.scala-lang.org/overviews/macros/typemacros.html), 2) программируемый вывод типов (http://docs.scala-lang.org/overviews/macros/inference.html).

Какие варианты пробовали, почему не понравились? По-хорошему, здесь надо написать паперу, а не пост. Сейчас я попробую очень вкратце выразить свои мысли. Я заранее извиняюсь, что кратко, но буду рад ответить на любые каменты в пределах своих знаний.

Классический способ метапрограммирования в Скале - вычисления на типах и имплисит параметрах. Я здесь совсем не специалист, но люди достигают потрясающих результатов. Управляют тайп инференсом, разбирают типы на запчасти, запрещают или разрешают вызовы методов, и т.д. Тут может больше рассказать уважаемый juan_gandhi. К сожалению, временами получается довольно тяжеловесная жесть: https://github.com/scalaz/scalaz/blob/59cfba6e8e293ec24c8b610ced057e0376c13a3f/core/src/main/scala/scalaz/Unapply.scala.

То, что мы пробовали вначале как основу для макросов, очень похоже на MacroML. Строго типизированные квазицитаты по имени reify (т.е. никаких тебе игр с биндингами), нет поддержки деструктурирования. В теории это, может, и выглядит хорошо (по разговору с Тахой - Walid Taha - я так понял, что он макросы в неконтролируемом смысле вообще не уважает), но на практике пока что народ не нашел способов юзать reify хоть отдаленно эффективно, т.к. почти всегда (см. use cases выше) нужны нетипизируемые квазицитаты (которые по отдельности смысла не имеют, а, будучи слепленными вместе, дают вменяемый результат).

К сожалению, это пока что все. Много времени уходит на реализацию, да и сейчас после двух недель багфиксов для 2.10.1, я буду скорее всего садиться за починку дурной, но очень важной детали реализации. Пока что мой план заключается в том, чтобы добиться легкости Немерле в реалиях нашего компилятора, а потом уже браться за новизну.