В свете недавнего обсуждения
механизмов работы линкаАвтор: xeno.by
Дата: 28.09.11
, пришла мысль обобщить
идею Скальского на тему декларативной активации макросов. Не пинайте, если сморожу глупость — я с макросами Немерле знаком совсем недолго.
Итак, к делу. Дополнительно к явному вызову макросов (функционально или атрибутами), возможно, имеет смысл реализовать автоматическую активацию макросов по шаблону. Скажем, встретился в коде вызов <[ $(foo: Foo).Bar(..$args) ]> — вызываем для него макрос, который, возможно, решит немножко подпилить или вообще заменить этот вызов. Некоторым образом, это напоминает лисповский defadvice, но мы не обязаны ограничиваться только подпиливанием определенных методов. С помощью этой фичи можно было бы реализовать "честную" поддержку LINQ — обращения к методам IQueryable автоматически бы вызывали преобразование выражений Немерле в выражения сишарпа.
Какие здесь могут быть проблемы? Будет ли это практично в реализации?
Здравствуйте, xeno.by, Вы писали:
XB>Итак, к делу. Дополнительно к явному вызову макросов (функционально или атрибутами), возможно, имеет смысл реализовать автоматическую активацию макросов по шаблону. Скажем, встретился в коде вызов <[ $(foo: Foo).Bar(..$args) ]> — вызываем для него макрос, который, возможно, решит немножко подпилить или вообще заменить этот вызов. Некоторым образом, это напоминает лисповский defadvice, но мы не обязаны ограничиваться только подпиливанием определенных методов. С помощью этой фичи можно было бы реализовать "честную" поддержку LINQ — обращения к методам IQueryable автоматически бы вызывали преобразование выражений Немерле в выражения сишарпа.
Все сильно сложнее чем ты себе можешь представить. Для замены кода вроде той что делается для поддержки преобразования в деревья выражений мало найти синтаксическую конструкцию. Для этого нужно вмешиваться в процесс типизации.
Вот когда в Н2 будет поддержка декларативного описания зависимостей между типами, то (я надеюсь) мы сможем это сделать. И никакой замены для этого не потребуется. Мы просто опишем перегрузку для синтаксиса вызова метода и определим для нее свои правила типизации.
XB>Какие здесь могут быть проблемы? Будет ли это практично в реализации?
Да море. Проще разобраться как оно реально работает, чем объяснять.
Если в двух словах, то конструкция $(foo: Foo).Bar очень перегружена. И без специального кода процесс резрешения перегрузки просто провалится, так как на входе мы имеем ссылку на функцию (с не выведенными типами), а на выходе нам нужно получить дерево выражений. И еще нужно догадаться, что такое преобразование требуется.
def (delegate_tc, exprTree_tc, argsTys, ret) =
if (IsFunctional(expr))
{
def (del, exprTree, args, ret) = TryExtractFunType(target.Hint);
if (args != null && ret != null)
{
def fnTy = FixedType.Fun(TypeVar.FromList(args), args.Length, ret);
def ok = expr.Type.TryRequire(fnTy);
if (ok)
{
_ = expr.Type.Require(fnTy);
(del, exprTree, args, ret)
}
else (null, null, null, null)
}
else (del, exprTree, args, ret)
}
else (null, null, null, null);
if (exprTree_tc != null && !SkipExpressionTreeConvertion)
{
match (expr)
{
| TExpr.MacroEnvelope(_, _, TExpr.DefFunctionsIn([fn], _), _)
| TExpr.DefFunctionsIn([fn], _) =>
def <[ fundecl: $_name[ ..$typarms1] (..$args1) : $ty1 where ..$tyconstrs1 $body1 ]> = fn.Parsed;
def trySetType(parm, ty)
{
when (parm.Type is PExpr.Wildcard)
parm.Type = PExpr.TypedType(ty)
}
mutable retTy = ty1;
when (!args1.IsEmpty && argsTys.Length == args1.Length)
{
NList.Iter2(args1, argsTys, trySetType);
when (retTy is PExpr.Wildcard)
retTy = PExpr.TypedType(ret);
}
def lamda = <[ fun [ ..$typarms1] (..$args1) : $retTy where ..$tyconstrs1 $body1 ]>;
def expr2 = TryTyping(() =>
TypeExpr(<[ Nemerle.Linq.ToExpression($lamda) ]>));
if (IsError (expr2) || !expr2.Type.TryRequire(target))
null
else
{
// The source texpr can be not typed. Help type it...
def fnTy = FixedType.Fun(TypeVar.FromList(argsTys), argsTys.Length, ret);
def ok = fn.decl.Type.Require(fnTy);
expr2.Type.ForceRequire(target);
_ = ok;
expr2
}
| _ => null
}
}
Большое спасибо за детали! Получается, в сто раз проще сделать поддержку макросов, которые могут вызываться в объектном стиле (например, через аннотацию, или через object macro ...).
Здравствуйте, xeno.by, Вы писали:
XB>Большое спасибо за детали! Получается, в сто раз проще сделать поддержку макросов, которые могут вызываться в объектном стиле (например, через аннотацию, или через object macro ...).
Да многое можно сделать. Только все это не так уж просто.
В Н2, думаю, мы будем поддерживать такие вещи как линк на уровне макросов (без хардкода в компиляторе). Но там будет очень не простое решение позволяющее описывать типизацию по непреобразованному АСТ. В Н1 такого нет. Есть только АМИ компилятора доступный из макросов. Через него такие вещи делать сложно (если вообще возможно).
По сути для реалзации LINQ в виде макроса нужно ввести специальный тип макроса — операция неявного приведения типов — расширение. Ну, как методы расширения и оператор приведения типов в одном флаконе. Это могло бы выглядеть примерно так:
// несинтаксический макрос описывающий оператор неявного приведения типа
macro method @:[T](expr : T) : Expression[T]
{
Лифтим в Expression...
}
Этот макрос похож на оператор неявного приведения типов:
public static @:[T](expr : T) : Expression[T]
{
преобразуем тип...
}
Компилятор будет включать в состав перегрузок в случаях когда требуется вставка неявного приведения типов.
Разрешение перегрузки будет происходить так же как для обычного метода, но в итоге, вместо вставки вызова метода будет вызвано тело макроса которое получит типизированное АСТ выражения (в параметре expr) и сможет произвести его лифтинг в АСТ деревьев выражений.