как работает лифтинг лямбд в линке?
От: xeno.by xeno.by
Дата: 28.09.11 19:10
Оценка:
Пытаюсь разобраться с тем, как работает линк в Немерле. Написал простенький код:

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, то лифтинг не происходит:

Nemerle.Compiler.Parsetree.PExpr+Call
() => a.AsQueryable().Where(el => el < x)
Call(func = Ref(name = =>), parms = Literal(val = ()), Call(func = Member(obj = Call(func = Member(obj = Ref(name = a), member = AsQueryable), parms = ), member = Where), parms = Call(func = Ref(name = =>), parms = Ref(name = el), Call(func = Ref(name = <), parms = Ref(name = el), Ref(name = x)))))
Nemerle.Compiler.Typedtree.TExpr+MacroEnvelope
macro_Nemerle.Core._N_operator905413823_4226Macro:
  def _N_lambda__3794() : System.Collections.Generic.IEnumerable[int]-
{
    block (_N_return) :
      DEBUG_INFO(System.Linq.Enumerable.Where.[int] (System.Func[int, bool] (macro_Nemerle.Core._N_operator905413823_4226Macro:
        def _N_lambda__3798(el : int) : bool
{
          block (_N_return) :
            DEBUG_INFO(<.s (el, x))
        }
        _N_lambda__3798)))
  }
  _N_lambda__3794


Если добавить ссылку на Nemerle.Linq.dll, т.е. + ncc -no-color -nowarn:10003 -nowarn:10005 -nowarn:168 -debug+ -r System.Core.dll -r Nemerle.Linq.dll -m macro.dll play.n playhelper.n -out play.exe, то лифтинг таки-отрабатывает:

Nemerle.Compiler.Parsetree.PExpr+Call
() => a.AsQueryable().Where(el => el < x)
Call(func = Ref(name = =>), parms = Literal(val = ()), Call(func = Member(obj = Call(func = Member(obj = Ref(name = a), member = AsQueryable), parms = ), member = Where), parms = Call(func = Ref(name = =>), parms = Ref(name = el), Call(func = Ref(name = <), parms = Ref(name = el), Ref(name = x)))))
Nemerle.Compiler.Typedtree.TExpr+MacroEnvelope
macro_Nemerle.Core._N_operator905413823_4226Macro:
  def _N_lambda__4298() : System.Linq.IQueryable[int]-
{
    block (_N_return) :
      DEBUG_INFO(System.Linq.Queryable.Where.[int] (macro_Nemerle.Linq.ToExpressionMacro:
        def closureFunc() : System.Linq.Expressions.Expression[System.Func[int, bool]]
{
          block (_N_return) :
            def _N_param_el__5784 = System.Linq.Expressions.Expression.Parameter (typeof (int), "el");
            (System.Linq.Expressions.Expression.Lambda.[System.Func[int, bool]] (System.Linq.Expressions.Expression.MakeBinary ((20 :> System.Linq.Expressions.ExpressionType), _N_param_el__5784, System.Linq.Expressions.Expression.Field (System.Linq.Expressions.Expression.Constant (ClosureObjectOf (x)), ClosureFieldOf (x)), false, null), array  [_N_param_el__5784]) : System.Linq.Expressions.Expression[System.Func[int, bool]])
        }
        def closureFuncRef = closureFunc;
        closureFuncRef ()))
  }
  _N_lambda__4298


При этом хочу отметить, что using Nemerle.Linq я нигде не пишу, поэтому я в недоумении. Кто вызывает ToExpressionMacro? System.Linq.Queryable.Where, по идее, не может этого делать, ибо это метод из библиотеки фреймворка. Макросы никакие я не импортирую. Объясните, пожалуйста.
Re: как работает лифтинг лямбд в линке?
От: hardcase Пират http://nemerle.org
Дата: 28.09.11 19:42
Оценка:
Здравствуйте, xeno.by, Вы писали:

XB>Кто вызывает ToExpressionMacro?


Это хардкод в компилятор.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: как работает лифтинг лямбд в линке?
От: xeno.by xeno.by
Дата: 28.09.11 19:50
Оценка:
А какие были варианты реализации лифтинга без хардкода?
Re[3]: как работает лифтинг лямбд в линке?
От: hardcase Пират http://nemerle.org
Дата: 28.09.11 19:53
Оценка:
Здравствуйте, xeno.by, Вы писали:

XB>А какие были варианты реализации лифтинга без хардкода?


Засунуть всю макру в компилятор.
Хардкод там тривиальный — если тип слева ExprssionTree, а тип справа с ним не совместим, то вставляется вызов этого макроса.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[4]: как работает лифтинг лямбд в линке?
От: xeno.by xeno.by
Дата: 28.09.11 19:59
Оценка:
H>Засунуть всю макру в компилятор.
Как бы это выглядело?

Вот вызываю я следующий код: a.AsQueryable().Where(el => el < x). Где здесь будет вызов макроса, если ничего не хардкодить? AsQueryable и Where — методы из стандартной библиотеки дотнета, до них не дотянуться. Их не сделать ни макросами, ни повесить на них ту штучку, которая описывается в разделе 7.4 Attaching macros to class members диссера Скальского.
Re[5]: как работает лифтинг лямбд в линке?
От: hardcase Пират http://nemerle.org
Дата: 28.09.11 20:11
Оценка:
Здравствуйте, xeno.by, Вы писали:

H>>Засунуть всю макру в компилятор.

XB>Как бы это выглядело?

Точно также, только Nemerle.Linq не пришлось бы подключать.

XB>Вот вызываю я следующий код: a.AsQueryable().Where(el => el < x). Где здесь будет вызов макроса, если ничего не хардкодить?


Ты не понял, "хардкод" внутри типизатора.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[6]: как работает лифтинг лямбд в линке?
От: xeno.by xeno.by
Дата: 28.09.11 20:12
Оценка:
А, ясно.
Re[5]: как работает лифтинг лямбд в линке?
От: VladD2 Российская Империя www.nemerle.org
Дата: 28.09.11 20:56
Оценка:
Здравствуйте, 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, который и осуществляет реальное преобразование. Собственно его реализацию можно найти здесь.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: как работает лифтинг лямбд в линке?
От: xeno.by xeno.by
Дата: 28.09.11 21:07
Оценка:
VD>Была у нас идя сделать макрос-расширения (как методы-расширения, но с подстановкой макросов вместо методов).
Ага, я тоже об этом подумал.


Еще было бы здорово, если бы можно было в одной единице компиляции совмещать макросы и обычный код. Чтобы, скажем, можно было в классе объявить обычные методы и макро-методы одновременно. В диссере написано, что авторы решили отказаться от совмещения из-за того, что легким движением руки можно превратить только что компилировавшийся код в кровавое месиво. Сейчас это принимается за аксиому или были дальнейшие мысли в этом направлении?
Re[7]: как работает лифтинг лямбд в линке?
От: VladD2 Российская Империя www.nemerle.org
Дата: 28.09.11 22:03
Оценка: 1 (1) +1
Здравствуйте, xeno.by, Вы писали:

XB>Еще было бы здорово, если бы можно было в одной единице компиляции совмещать макросы и обычный код.


Не уверен в этом. Это, конечно, повысило бы частоту написания макросов, но качество их резко снизилось бы. И проблем увеличилось бы.

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


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

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

Как показывает практика даже при четкой границе у многих рвет мозг. А при невидимо границе кое-кто может сойти с ума.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: как работает лифтинг лямбд в линке?
От: xeno.by xeno.by
Дата: 29.09.11 06:14
Оценка: 16 (2)
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. Из-за этого некоторые реализации Схемы снова забили на разделение стадий компиляции => мегапроблема для портабельности кода между реализациями.

 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.