September 18th, 2011

glider

макросы: обзор литературы

Зачел сегодня две фундаментальные работы по макросам в Немерле: "Syntax-Extending and Type-Reflecting Macros in an Object-Oriented Language" (далее "диссер") и "Язык Немерле, часть 5" (далее "статья"), а также за эти несколько дней поговорил с Владом Чистяковым (спасибо большое, Влад, за желание помочь и детальные объяснения!). Несколько промежуточных выводов:

1) Квазицитирование крайне изящно. Всего три прозрачные языковые конструкции (цитирование, антицитирование и сплайсинг, см. раздел 8.3 диссера, если покороче, или раздел "Квази-цитирование" статьи, если развернуто) раз эдак в сто упрощают парсинг и генерацию AST. Особенно впечатлило использование антицитат и сплайсов в паттерн-матчинге. Внутренняя красота квазицитирования особенно остро воспринимается после мучений с композируемостью линка.

2) Впрочем одна знакомая до боли проблема остается. Как лифтить (т.е. преобразовывать в AST) код из внешнего мира? Адептам стейджинга в этом плане просто (кстати, рекомендую презентацию по ссылке, первая работа Олега, которую я понял) - все функции и значения, имеющие стейджнутый тип, они лифтят (явным образом в стиле tagful или неявно через tagless - что это значит см. линк или спрашивайте у меня), а остальное игнорируют. Достигается это ценой синтаксического оверхеда на типизацию + возможными издержками на tagful представление кода (см. сюда за подробностями), но зато ведь достигается. А с макросами сложнее - в большинстве случаев без явного антицитирования они даже не пошевелятся что-то залифтить, да и, самое главное, код, скомпилированный без лифтинга, (например, внешние библиотеки) уже никогда не сможет быть залифченным. Также появляется неочевидная дилемма на тему того, разрешать ли макросам видеть значения в лексическом скоупе (см. раздел 2.3.3 диссера).

3) Также совершенно непонятно как сделать ортогональной кодогенерацию на макросах в статически типизированном ОО-языке. Во-первых, скорее всего придется выносить макросы в отдельный юнит компиляции. Во-вторых, появляется дилемма: юзать макросы до типизации и иметь возможность нагенерировать новые типы, или юзать типизированные AST, но мириться с залоченным пространством типов (лучше даже не думать, что будет, если сюда добавить тайп-инференс). В-третьих, объектность подливает масла в огонь в плане того, что появляются классы (концептуально новая сущность, которая не композируется с функциями) и наследование. В итоге получается кошмар стейтфул программирования, когда в зависимости от фазы луны операции над данными то разрешены, то запрещены. Неудивительно, что создатели F# попросту скипнули кодогенерацию, а реализовали только read-only квазицитаты.

4) В плане сообщений об ошибках и отладки сгенерированного во время раскрытия макросов кода все не так плохо, как может показаться на первый взгляд. Вместе с генерацией кода мы можем сгенерировать для него исходник (AST-то у нас есть), к которому привяжем дебажную информацию. Само собой, повреждаются оригинальные номера строк (их-то мы сможем восстановить линзами, вот только пошаговый дебаг будет прыгать туда-сюда как ненормальный) и получается фигня с стек-трейсами, но это уже хоть что-то. У сторонников стейджинга все гораздо хуже (см., например, страничку 15 слайдов про LMS).

5) Кроме того для языка с синтаксисом макросам желательно иметь контроль над парсером (см. слинкованные креативы про Немерле, а также технический репорт "Genuine, Full-power, Hygienic Macro System for a Language with Syntax"). В таком случае можно реализовывать полезные вещи вроде добавления своих атрибутов к декларациям (типа async method или method precondition) или введения кастомного синтаксиса. upd. Вообще-то, без этого можно жить: первое решается при помощи аннотаций, которые будут запускать соответствующий макрос, некоторое подмножество второй фичи уже есть в скале (например, см. каноничную реализацию цикла while).

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