Пытаюсь разобраться с тем, как работает линк в Немерле. Написал простенький код:
using System;
using System.Linq;
using System.Console;
def x = 11;
def a = [10];
PrintExpr(() => a.AsQueryable().Where(el => el < x));
// в другой единице компиляции есть вот такой макросmacro PrintExpr(pexpr : expr)
{
WriteLine(pexpr.GetType());
WriteLine(pexpr.ToString());
pexpr.Dump();
def typer = Nemerle.Macros.ImplicitCTX();
def texpr = typer.TypeExpr(pexpr);
WriteLine(texpr.GetType());
WriteLine(texpr.ToString());
// texpr.Dump();
<[ () ]>;
}
Если компилировать этот код, не ссылаясь на Nemerle.Linq.dll, т.е. ncc -no-color -nowarn:10003 -nowarn:10005 -nowarn:168 -debug+ -r System.Core.dll -m macro.dll play.n playhelper.n -out play.exe, то лифтинг не происходит:
При этом хочу отметить, что using Nemerle.Linq я нигде не пишу, поэтому я в недоумении. Кто вызывает ToExpressionMacro? System.Linq.Queryable.Where, по идее, не может этого делать, ибо это метод из библиотеки фреймворка. Макросы никакие я не импортирую. Объясните, пожалуйста.
Здравствуйте, xeno.by, Вы писали:
XB>А какие были варианты реализации лифтинга без хардкода?
Засунуть всю макру в компилятор.
Хардкод там тривиальный — если тип слева ExprssionTree, а тип справа с ним не совместим, то вставляется вызов этого макроса.
H>Засунуть всю макру в компилятор.
Как бы это выглядело?
Вот вызываю я следующий код: a.AsQueryable().Where(el => el < x). Где здесь будет вызов макроса, если ничего не хардкодить? AsQueryable и Where — методы из стандартной библиотеки дотнета, до них не дотянуться. Их не сделать ни макросами, ни повесить на них ту штучку, которая описывается в разделе 7.4 Attaching macros to class members диссера Скальского.
Здравствуйте, xeno.by, Вы писали:
H>>Засунуть всю макру в компилятор. XB>Как бы это выглядело?
Точно также, только Nemerle.Linq не пришлось бы подключать.
XB>Вот вызываю я следующий код: a.AsQueryable().Where(el => el < x). Где здесь будет вызов макроса, если ничего не хардкодить?
Здравствуйте, xeno.by, Вы писали:
H>>Засунуть всю макру в компилятор. XB>Как бы это выглядело?
Как в C#.
XB>Вот вызываю я следующий код: a.AsQueryable().Where(el => el < x). Где здесь будет вызов макроса, если ничего не хардкодить?
a.AsQueryable().Where(ToExpression(el => el < x))
XB>AsQueryable и Where — методы из стандартной библиотеки дотнета, до них не дотянуться.
Была у нас идя сделать макрос-расширения (как методы-расширения, но с подстановкой макросов вместо методов). Вот тогда можно было бы сделать библиотеку аналогичную линку, которая бы работала без хардкодинга.
Кроме того в Н2 планируется реализовать перегрузку макросов на основании типов
XB> Их не сделать ни макросами, ни повесить на них ту штучку, которая описывается в разделе 7.4 Attaching macros to class members диссера Скальского.
Ну, не обязательно же родные методы звать? Можно сделать аналогичные обертки.
Ну, а для "честной" подмены, как в C#, нужно влезать на уровне типизации. Конкретно, нужно перехватывать обработку поиска неявного приведения типов. Считай, что в компилятор встроена поддержка неявного приведения типов от делегата к деревьям выражений. Оно тупо вызывает макрос ToExpression, который и осуществляет реальное преобразование. Собственно его реализацию можно найти здесь.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VD>Была у нас идя сделать макрос-расширения (как методы-расширения, но с подстановкой макросов вместо методов).
Ага, я тоже об этом подумал.
Еще было бы здорово, если бы можно было в одной единице компиляции совмещать макросы и обычный код. Чтобы, скажем, можно было в классе объявить обычные методы и макро-методы одновременно. В диссере написано, что авторы решили отказаться от совмещения из-за того, что легким движением руки можно превратить только что компилировавшийся код в кровавое месиво. Сейчас это принимается за аксиому или были дальнейшие мысли в этом направлении?
Здравствуйте, xeno.by, Вы писали:
XB>Еще было бы здорово, если бы можно было в одной единице компиляции совмещать макросы и обычный код.
Не уверен в этом. Это, конечно, повысило бы частоту написания макросов, но качество их резко снизилось бы. И проблем увеличилось бы.
XB>Чтобы, скажем, можно было в классе объявить обычные методы и макро-методы одновременно. В диссере написано, что авторы решили отказаться от совмещения из-за того, что легким движением руки можно превратить только что компилировавшийся код в кровавое месиво. Сейчас это принимается за аксиому или были дальнейшие мысли в этом направлении?
Даже в Схеме — это вызывает разрыв мозга у многих программистов. А это, ведь, динамический язык в котором граница между рантаймом и компиляцией сильно размыта. А в статически типизируемом языке это вызовет еще больше проблем.
Основная проблема в том, что компилятору все равно пришлось бы выявлять часть кода относящуюся к метакоду, и компилировать ее перед запуском макроса. Опустим тот факт, что выявление метакода не самая простая задача и предположим, что компилятор это делать умеет. Но что делать пользователям? У них же то и дело будет путаница между тем какой код является метапрограммой, а какой целевой.
Как показывает практика даже при четкой границе у многих рвет мозг. А при невидимо границе кое-кто может сойти с ума.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VD>Как показывает практика даже при четкой границе у многих рвет мозг. А при невидимо границе кое-кто может сойти с ума.
Отличное наблюдение! Очень полезно знать.
Кстати, да, схемерам хорошо. Во-первых, Схема часто только интерпретируемая, а тогда компайл-тайм и ран-тайм вообще живут в одном аппдомене, т.е. можно иметь бесплатный cross-stage persistence. Во-вторых, им не надо ничего строго типизировать, поэтому не надо ломать голову, что относится к метакоду, а что нет — в ран-тайме разберемся.
Но есть нюансы. Начиная с некоторого времени Схема таки-стала опционально компилируемой. И тогда возникли две проблемы. Первая (специфичная для Схемы) связана с тем, что есть, фактически, две идиомы подключения зависимостей — для ран-тайма (load ...) и для компайл-тайма (eval-when (compile) (load ...)) и непонятно как их смешивать. Вторая (более общая) — проблема порядка компиляции. Скажем, когда компилируем все модули вместе, все ок. А когда компилируем каждый по-отдельности, выясняется, что первый модуль в процессе компиляции создавал какие-то in-memory структуры, от которых зависел второй, и теперь второй модуль не компилируется (или, что еще хуже, компилируется неправильно). Вот здесь эта штука описывается в деталях: http://www.cs.utah.edu/plt/publications/macromod.pdf.
В качестве решения этих проблем заставили народ в B&D стиле явно разграничивать фазы. Для этого размножили оператор require (как я понимаю, это аналог using). Раньше он был только один, а теперь появилось два: require (для ран-тайма) и require-for-syntax (для компайл-тайма). Последнее название может показаться странным, но оно вполне логично, если учесть, что в Схеме макросы объявляются с помощью оператора define-syntax. Вроде бы все стало ок, но появилась другая проблема, описанная вот тут: http://www.phyast.pitt.edu/~micheles/scheme/scheme22.html. Из-за этого некоторые реализации Схемы снова забили на разделение стадий компиляции => мегапроблема для портабельности кода между реализациями.