Виды макросов в Nemerle 2.0
От: catbert  
Дата: 01.11.10 19:02
Оценка:
Допустим, я хочу реализовать сишарповский dynamic в Nemerle 2. Для этого мне нужно перехватывать все вызовы методов для всех объектов типа dynamic в компилируемом проекте. Или добавить Extension Method, который на самом деле был бы макросом.

Для этого необходимо иметь возможность расширять Nemerle на уровне вызовов метода.

Еще я хочу создавать новые top-level сущности, как таблицы в Falcon-е. Это, по идее, позволяет новый парсер Nemerle.

Каких видов макросов, кроме трех стандартных видов макросов Nemerle, вам не хватает?
Re: Виды макросов в Nemerle 2.0
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.11.10 19:53
Оценка:
Здравствуйте, catbert, Вы писали:

C>Допустим, я хочу реализовать сишарповский dynamic в Nemerle 2. Для этого мне нужно перехватывать все вызовы методов для всех объектов типа dynamic в компилируемом проекте.


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

C>Или добавить Extension Method, который на самом деле был бы макросом.


Об этом мы думали, но так и не реализовали. В принципе возможность была бы полезной.

C>Для этого необходимо иметь возможность расширять Nemerle на уровне вызовов метода.


C>Еще я хочу создавать новые top-level сущности, как таблицы в Falcon-е. Это, по идее, позволяет новый парсер Nemerle.


С парсером особых проблем нет. Синтаксис ведь не меняется. Тут проблема в том, что у кода меняется семантика в зависимости от типа выражения. А это уже на синтаксисе не пропрешь.
Тут можно поступить так... Объявить dynamic как макрос оразующий область видимости. И уже в этой области видимости искать вхождения dynamic-переменной и менять код. Но это сложно.

C>Каких видов макросов, кроме трех стандартных видов макросов Nemerle, вам не хватает?


Думаю, что в 2.0 будет всего хватать. А пока что придется обходиться тем что есть.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Виды макросов в Nemerle 2.0
От: Аноним  
Дата: 02.11.10 07:10
Оценка:
VD>Думаю, что в 2.0 будет всего хватать. А пока что придется обходиться тем что есть.

Когда планируется выход 2.0, приблизительно — через год, два, 10 лет ?
Re[2]: Виды макросов в Nemerle 2.0
От: Silver_s Ниоткуда  
Дата: 08.11.10 18:03
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Думаю, что в 2.0 будет всего хватать. А пока что придется обходиться тем что есть.


Возможно ли упростить как-то макросы в плане стадий BeforeInheritance...WithTypedMembers, откладывания DelayMacro, проверок первый раз выполняется или нет , типизаций вручную.
Не то чтобы лишнее писать лень, а как-то сейчас это багоопасно, и даже неконсистентно.

Скажем, чтобы можно было указать, что макрос на вход требует полностью типизированное выражение. А компилятр сам откладывает вызов насколько нужно, и сам типизирует. Сейчас же даже для стадий WithTypedMembers, приходят нетипизированные выражения (по крайней мере иногда) и вручную надо вызывать типизацию.
И тоже для IsMainPass, ErrorMode, нельзя ли убрать необходимость их использования?
Например, не очевидно, что для макроатрибута уровня сборки, добавляющей класс на стадии WithTypedMembers, не нужно проверять IsMainPass, и так все компилируется.
Короче говоря возможно ли сделать макросы для обезьян , не то чтобы не знающих синтаксис, а хотя-бы не знающих все внутренности компилятора. Методом тыка можно подобрать работающие комбинации, но какая-то неуверенность, что ничего не сломается.
Re[3]: Виды макросов в Nemerle 2.0
От: catbert  
Дата: 08.11.10 19:37
Оценка:
Здравствуйте, Аноним, Вы писали:

VD>>Думаю, что в 2.0 будет всего хватать. А пока что придется обходиться тем что есть.


А>Когда планируется выход 2.0, приблизительно — через год, два, 10 лет ?


У меня какое-то чувство дежа вю...
Re[3]: Виды макросов в Nemerle 2.0
От: VladD2 Российская Империя www.nemerle.org
Дата: 08.11.10 21:24
Оценка: 3 (1)
Здравствуйте, Silver_s, Вы писали:

Для начала отвечу на самый конец:
S_> Короче говоря возможно ли сделать макросы для обезьян , не то чтобы не знающих синтаксис, а хотя-бы не знающих все внутренности компилятора. Методом тыка можно подобрать работающие комбинации, но какая-то неуверенность, что ничего не сломается.

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

S_> Возможно ли упростить как-то макросы в плане стадий BeforeInheritance...WithTypedMembers, откладывания DelayMacro, проверок первый раз выполняется или нет , типизаций вручную.

S_>Не то чтобы лишнее писать лень, а как-то сейчас это багоопасно, и даже неконсистентно.

Для начала нужно отделит мух от котлет. BeforeInheritance...WithTypedMembers — это стадии компиляции применимые для макро-атрибутов, т.е. макросов работающих на АСТ верхнего уровня (типов и их членов).

WithTypedMembers означает, что макрос должен работать после того как параметры методов будут связаны с типами.

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

Так что по сути работать с типизированными выражениями вообще нельзя. Чтобы обойти это ограничение есть возможность произвести типизацию вручную (путем ручного вызова метода TypeExpr() объекта Typer.
К сожалению, вывод типов операция не линейная. Не всегда можно вывести типы сразу. Куски АСТ которые не могут быть стипизированы в первом проходе откладываются в очередь отложенной типизации, которая разбирается после того как тело метода будет типизировано.

К сожалению — это усложняет разработку макросов которым требуется информация о типах к коду.

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

Так вот такой макрос можно было реализовать в два прохода следующим образом.
На первом проходе мы формируем обобщенный код основанный на том факте, что все коллекции в дотнете реализуют интерфейс IEnumerable[T]. По сему в стандартной библиотеке мы можем положить метод вида:
module X
{
  public ForEach(source : IEnumerable[T], T -> void) { ... }
}

Его можно использовать для формирования заглушки (упрощенно):
[BeforeTyping]
macro @foreach(pattern : Pattern, collection : PExpr, body : PExpr) : PExpr
syntax: "foreach" "(" pattern "in" collection ")" body // это планируемый новый синтаксис для синтаксических макросов
{
  <[ X.ForEach($collection, $pattern => $body) ]>
}

[AfterTyping]
macro @foreach(pattern : Pattern, collection : PExpr, body : PExpr) : PExpr
{
  match (collection.Type.Fix())
  {
    | FixedType.Array(elemType, rank) => генерируем реализацию для массива
    | FixedType.Class(typeInfo, elemType) =>
      if (typeInfo.TryRequire(<[ ttype: list[_] ]>))
        генерируем реализацию для списка
      else if (MatchEnumPattern(typeInfo))
        генерируем реализацию для типа удовлетворяющего паттерну "перечислитель"
      else if (typeInfo.TryRequire(<[ ttype: IEnumerable[_] ]>))
        генерируем реализацию для IEnumerable
      ...
  }
}

Вначале будет вызвана версия макроса помеченная атрибутом BeforeTyping. Далее компилятор типизирует выражение возвращенное им и дождавшись момента когда все типы внутри этого выражения будут выведены вызовет версию этого же макроса помеченную атрибутом AfterTyping.
Ей будет переданы те же параметры что и версии помеченной атрибутом BeforeTyping, но при этом в них будет полностью доступна информация о типах.

Думаю у многих возник вопрос — "Зачем нужна первая стадия?". Дело в том, что чтобы вывести типы компилятор должен что-то типизироваться. Причем типизироваться просто отдельные выражения нельзя, так как он не знает как связаны между собой выражения из отдельных параметров. Если вернуться к нашему примеру, то pattern будет содержать имя переменной которая с одной стороны должна использоваться внутри body, а с другой быть совместима с типом элемента коллекции. Формируя выражение <[ X.ForEach($collection, $pattern => $body) ]> и передавая его типизатору мы и определяем эти самые зависимости.

Так вот единственная цель этого код — образовать эти зависимости и позволить компилятору автоматически и полностью типизировать код переданный макросу в качестве параметров.

S_> Скажем, чтобы можно было указать, что макрос на вход требует полностью типизированное выражение. А компилятр сам откладывает вызов насколько нужно, и сам типизирует. Сейчас же даже для стадий WithTypedMembers, приходят нетипизированные выражения (по крайней мере иногда) и вручную надо вызывать типизацию.


В принципе это и сейчас можно сделать. В Typer-е есть метод:
    /// Wait for all types for subexpressions will be inferred and all nested
    /// delay typing action will be resolved. Do transformation after that.
    /// If nested subexpressions contains fesh (not inferred) types, 
    /// typing errors or delay typing action the transformation not do.
    public TransformWhenAllTypesWouldBeInfered(
      transform  : PExpr * TExpr -> PExpr,
      tExpr      : TExpr, 
      expr       : PExpr = null
    )
      : PExpr

Можно передать ему tExpr полученый путем вызова метода TypeExpr() и когда все типы выведутся он вызовет transform передав ему стипизированное выражение.

Это похоже на то что я описывал выше, но работает уже сейчас. Его применение можно наблюдать в реализации LINQ-а.

S_> И тоже для IsMainPass, ErrorMode, нельзя ли убрать необходимость их использования?


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

S_>Например, не очевидно, что для макроатрибута уровня сборки, добавляющей класс на стадии WithTypedMembers, не нужно проверять IsMainPass, и так все компилируется.


Ну, вот с этим это и связано. Надо просто запомнить если есть атрибуты вроде WithTypedMembers то IsMainPass использовать не надо.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Виды макросов в Nemerle 2.0
От: Silver_s Ниоткуда  
Дата: 11.11.10 14:53
Оценка:
Здравствуйте, VladD2, Вы писали:

А в этом примере что не так? Если в двух словах, что там за NullReferenceException?

[MacroUsage (MacroPhase.WithTypedMembers, MacroTargets.Assembly, Inherited = true)]
macro AsmMacro (ex)
{         
    def ctx = Nemerle.Macros.ImplicitCTX ();     
    ctx.TypeExpr(ex); //NullReferenceException
    //...
}


При таком использовании работает:
[assembly: MacroLibrary1.AsmMacro(typeof(int))]

И при таком тоже
[assembly: MacroLibrary1.AsmMacro(3)]

А на таком или более сложных выражениях NullReferenceException
[assembly: MacroLibrary1.AsmMacro(3+1)]
Re[5]: Виды макросов в Nemerle 2.0
От: VladD2 Российская Империя www.nemerle.org
Дата: 11.11.10 15:45
Оценка:
Здравствуйте, Silver_s, Вы писали:

S_>Здравствуйте, VladD2, Вы писали:


S_> А в этом примере что не так? Если в двух словах, что там за NullReferenceException?


S_>
S_>[MacroUsage (MacroPhase.WithTypedMembers, MacroTargets.Assembly, Inherited = true)]
S_>macro AsmMacro (ex)
S_>{         
S_>    def ctx = Nemerle.Macros.ImplicitCTX ();     
S_>    ctx.TypeExpr(ex); //NullReferenceException
S_>    //...
S_>}
S_>


Посмотрел под отладчиком... В общем-то это косяк в компиляторе.
Просто никто не предположил, что кто-то будет компилировать код вне контекста какого-либо типа.

В ближайшее время пофиксим это дело.

Пока что воргэранут следующий:
      def typer = Typer(typer.Env);
      _ = typer.TypeExpr(ex);
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.