Re: Макрос для пересоздания структур
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.08.07 11:34
Оценка: 16 (1)
Здравствуйте, Алексей П, Вы писали:

АП>Макрос выводит тип a, ищет подходящий конструктор и подменяет нужные параметры. Всё просто.


Сразу одно замечание. Тип может не вывестись в месте применения. В таком случае нужно исползовать отложенное выполнение макроса. О том как это сделать я как раз написал во второй части статьи "Макросы Nemerle – расширенный курс"
Автор(ы): Чистяков Влад (VladD2)
Дата: 31.07.2007
Во второй части статьи о макросах Nemerle речь пойдет о макросах уровня выражения, о макросах, изменяющих синтаксис языка, а также о контексте компиляции, доступном в макросах, и тех возможностях, которые он предоставляет (типизации выражений, получении доступа к описанию типов проекта, информации о методах и т.п.).
. Она пока не выложена на сайт. Так что процитирую этот фрагмент статьи:

Откладывание выполнения макроса до момента, когда информация о типах становится доступной
Не будет преувеличением сказать, что на сегодня Nemerle обладает самым сложным механизмом вывода типов, по крайней мере, среди гибридных языков, т.е. языках программирования, поддерживающих ООП и ФП.
Система вывода типов Nemerle может выводить типы из использования. Причем использование, определяющее (или уточняющее) тип переменной, может быть хоть самым последним выражением метода. Более того, оно может быть косвенным. Классическим примером мощности вывода типов в Nemerle является работа с Dictionary[K, V]:

using System;
using System.Console;
using System.Collections.Generic;

def dic = Dictionary();
WriteLine(dic.GetType().FullName);
dic.Add("Текущая дата", DateTime.Now);

Этот код выведет на консоль:
System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.
0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.DateTime, mscorlib,
 Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

Заметьте, что хотя в примере не указано явно ни одного параметра типа для Dictionary, компилятор все равно прекрасно выводит тип переменной.
Данный пример еще очень прост. На практике переменная еще до ее какой-либо инициализации может передаваться в перегруженный метод. При этом для определения того, какой из методов должен быть вызван, компилятор должен знать, что за тип у переменной, переданной методу в качестве параметра. Это заставляет компилятор вычислять типы переменных рекурсивно. В Typer-е есть список выражений, ожидающих типизации (содержащих так называемые неразрешенные переменные типов). Когда Typer завершает первый проход по методу, он просматривает этот список, и если он не пуст, но изменен по сравнению с предыдущим проходом, снова запускает процесс типизации. На каждом цикле типизации могут выявляться типы тех или иных переменных, что позволяет на следующих циклах типизации использовать эту информацию для выявления типов у других (связанных с этими) переменных. По сути, компилятор строит набор отношений между переменными (точнее, их типами) и пытается последовательно решить головоломку типов.
Если код корректен, то рано или поздно список выражений, ожидающих типизации, становится пустым, и процесс типизации оканчивается. Если программа содержит код, в котором невозможно определить типы (содержит циклические зависимости или просто ошибки), то компилятор выдает сообщения об ошибках и останавливает процесс типизации.
Как все это касается разработчиков макросов? А самым наипрямейшим образом. Дело в том, что процесс раскрытия макросов проходит как раз во время типизации. Когда компилятор последовательно разбирает все ветки нетипизированного AST (т.е. цепочку вариантов PExpr), типизирует их, и, если встречается PExpr.MacroCall, пытается раскрыть макрос, и типизировать получившееся в результате его выполнения выражение (имеющее опять же тип PExpr). Если это выражение тоже содержит обращения к макросам, то раскрываются и они.
Для выражений, находящихся до раскрываемого макроса, компилятор пытается вычислить типы, но, как уже говорилось, это не всегда возможно. Поэтому во время раскрытия макроса не вся информация о типах может быть доступна.
Чтобы продемонстрировать сказанное, создадим макрос, пытающийся вычислить информацию о типах выражения и выводящий эту информацию в консоль IDE. Вот его код:
macro PrintExpressionType(expr)
{
  def typer = Nemerle.Macros.ImplicitCTX();
  def tExpr = typer.TypeExpr(expr);
  
  def msg = $"Во время работы макроса тип '$tExpr' "
    + match (tExpr.Type.Hint)
      {
        | Some(ty) => $"известен: $(ty)."
        | None     =>  "НЕизвестен!"
      };
      
  Message.Hint(msg);
}

Теперь попытаемся применить его. В следующем случае:
mutable x = array[0];
PrintExpressionType(x);

Макрос выведет в консоль IDE:
Во время работы макроса тип 'x' известен: array [int-].

Однако стоит немного изменить пример:
mutable x = null;
PrintExpressionType(x);
x = array[0];

и макрос, что называется, «обломается».
Во время работы макроса тип 'x' НЕизвестен!

Что же делать?
Можно заставить компилятор отложить вычисление макроса, как говорится, до лучших времен. Делается это с помощью метода DelayMacro все того же контекста макроса (т.е. объекта типа Ty-per).
Изменим макрос следующим образом:
macro PrintExpressionType(expr)
{
  def typer = Nemerle.Macros.ImplicitCTX();
  def tExpr = typer.TypeExpr(expr);
  
  def msg = $"Во время работы макроса тип '$tExpr' "
    + match (tExpr.Type.Hint)
      {
        | Some(ty) => $"известен: $(ty)."
        | None     => "НЕизвестен!"
      };
      
  Message.Hint(msg);
    
  def result = typer.DelayMacro(fun (fail_loudly)
  {
    def tExpr = tExpr;
    match (tExpr.Type.Hint)
    {
      | Some(ty) =>
        // do something with the type
        Message.Hint($"Внутри DelayMacro тип для '$tExpr' известен: $(ty).");
        Some(<[ () ]>)

      | None =>
        when (fail_loudly)
          Message.Error(expr.loc, $ "невозможно вывести тип для '$expr'");
        
        None()
    }
  });

  result
}

И снова выполним код:
mutable x = null;
PrintExpressionType(x);
x = array[0];

На этот раз макрос выведет в консоль IDE следующее:
Во время работы макроса тип 'x' НЕизвестен!
Внутри DelayMacro тип для 'x' известен: array [int-].

Метода DelayMacro имеет следующую сигнатуру:
DelayMacro(resolve : bool -> option[PT.PExpr], 
           expected : TyVar = null) : PT.PExpr

Ему можно задать переменную типа, описывающую какой тип ожидается (если она не задана, то используется «свежая» переменная типа, допускающая любой тип), и функцию, которая будет вызываться, когда компилятор будет делать повторные проходы типизации.
По сути, метод DelayMacro возвращает слоеный пирог:
PExpr.Typed(TExpr.Delayed(...))

в который закладывается ссылка на Typer, переданную нами ссылку на функцию, локальный контекст, переменную типа и другие необходимые данные. Компилятор, встретив PExpr.Typed, просто разворачивает его содержимое и помещает в конструируемый им типизированный AST. На следующем цикле типизации компилятор обнаруживает TExpr.Delayed и пытается вызывать переданную нами ссылку на функцию. Если функция возвращает None(), то компилятор пытается вызвать ее на следующем витке типизации, и так далее. Если функция возвращает Some(PExpr(...)), то PExpr раскрывается и типизируется. Естественно, что при формировании этого PExpr мы вольны использовать уже доступную информацию о типах.
Таким образом, DelayMacro позволяет нам дождаться появления необходимой информации о типах и сгенерировать код на ее основе. Это действительно мощнейшая возможность, использование которой упрощается использованием лямбд и замыканий.


Если нужно для дела (и нет бумажной версии журнала), то могу выслать статью по мылу.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.