stdray: (Default)
stdray ([personal profile] stdray) wrote2013-02-27 11:29 am

Немного макросов-операторов на Nemerle

Бывает, ради эксперимента пишу какие-то макросы, чтобы разобраться что позволяет макро-подсистема Nemerle, а что - нет. Если будет получаться что-то забавное, стану выкладывать это сюда.


Расширяемые анонимные типы

По мотивам хотелок [personal profile] metaclass. На сколько я понял, ему нужны анонимные типы, которые можно расширять, добавляя новые элементы, а также передавать за пределы той области видимости, в которой они были объявлены. В языке Nemerle нет анонимных типов, однако ими все пользуются, поскольку они реализованы через макросы.

def obj = new(name = "anon", age = 10, created = DateTime.Now);


В общий чертах, работает это так: макрос new по переданным параметрам генерирует генерик-класс с макро-атрибутом [Record] вида

[Record] class AnonType[name, age, created] {

public name : name;

public age : age;

public created : created;

}


Макрос [Record] создает конструктор с аргументами для всех полей, а потом вызов макроса переписывается в конструктор только что созданного класса.
После раскрытия макроса new
Это очень красивое решение, на мой взгляд, потому что объем работы сравнительно небольшой, а всю остальную заботу берет на себе компилятор, типизируя переменные и конкретизируя обобщенный класс.
типизированное раскрытие new
Я подумал, что раз подобная работа уже была кем-то проделана, то сделать расширяемые анонимные типы будет несложно. Для этого надо узнать тип объекта, который планируется расширить новыми полями, получить информацию о его конструкторе, а потом сложить параметры конструктора с новыми аргументами, а потом отдать все это макросу new.

[assembly: Nemerle.Internal.OperatorAttribute ("NemerleExperiments", "<+", false, 141, 140)]

public macro @<+(var: PExpr, extends : PExpr) {

ExtendedAnonTypesImpl.ExtendBy(Macros.ImplicitCTX(), var, extends)

}

module ExtendedAnonTypesImpl{

public ExtendBy(typer: Typer, var : PExpr, extends: PExpr) : PExpr {

Macros.DefineCTX(typer);

def varName = UtilM.GetName <| var;

def newFields = UtilM.Expr2List <| extends;

def buildExtendCtor(t) {

def flags = BindingFlags.Public | BindingFlags.Instance;

def ctorParams = t.Type.TypeInfo.GetConstructors(flags).Single().GetParameters();

def oldFields = ctorParams.Map(fun(p) {

def pname = <[$(p.Name : usesite)]>;

<[ $pname = $(varName : name).$pname ]> : PExpr

});

def allFields = oldFields + newFields;

<[ Nemerle.Extensions.AnonymousClassNormalCtor(..$allFields) ]>;

}

UtilM.WithType(typer, var, buildExtendCtor)

}

}


Использовать можно в сочетании с макросом new (в принципе, с любым типом, конструктор которого создан макросом Record)
Вот так можно расширять анонимные типы
К сожалению, информация о типе будет доступна только в пределах функции или свойства уровня класса, где все это происходит. То есть из локальной функции наверх или в параметры другой локальной функции передать данные без потери информации о типе можно, а вот за пределы члена класса эту информацию я вытащить не смог. Хотя и весьма настырно пытался. Дело в том, что методы класса типизируюся до того, как начинается работа с их телами, когда будет раскрыт макрос new. Ну и вроде бы задача неразрешима, но в одном конкретном случае мне все же удалось пробросить информацию наверх. Может быть, лучшее понимание работы компилятора поможет обобщить это костылик.


Инфиксная форма функции от двух аргументов

По мотивам Haskell с его поистине безграничным синтаксическим сахаром из коробки. Еще давно, когда я только пришел на форум к немерлистам, выяснять чего же умеют в плане синтаксиса макросы Nemerle, я задавал вопрос, как сделать тернарный оператор вида аргумент1 `функция` аргумент2, который бы переписывался в обыкновенный вызов этой функции. Мне сказали, что кроме унарных и бинарных операторов никаких других не предусмотрено, и я успокоился. А тут подумал, что ничего не мешает мне объявить оператор `, а закрывающий ` поймать руками. Ничего и не помешало

[assembly: Nemerle.Internal.OperatorAttribute ("NemerleExperiments", "`", false, 300, 284)]

macro @`(larg, rigth) {

def (f, rarg) = match(rigth) {

| <[ $e1 ` $e2 ]> => (e1, e2);

| _ => Message.FatalError(rigth.Location, "Expected `");

}

<[ $f($larg, $rarg) ]>

}


Единственное, что вызывает сомнения - это корректность выбранной силы связывания. Выражение слева будем забирать последними, если кому-то надо - пусть ставит скобки. А вот справа, необходимо активней поглощать токены, чем . (точка), иначе невозможно будет оборачивать этим оператором функции, находящиеся в модуле или пространстве имен.
Инфиксная форма функции от двух аргументов
Работает как и ожидалось, хотя, я думаю, фича абсолютно бесполезная. Думал, в каких ситуациях это удобно использовать и не придумал ничего. Правда, когда имеешь дело с силой связывания, смотришь силы стандартных операторов, голова идет кругом и в голове только "нинужна".


Именованная лямбда

По мотивам АНАФОРИЧЕСКОЙ ЛЯБДЫ. В моем случае, речь идет о конструкции, которая выглядит как лямбда-выражение, ведет себя, как лямбда-выражение, но при этом имеет имя, по которому к ней можно обратиться. Не очень понятно, что тут можно прокомментировать, берем выражение и переписываем его в лямбду, внутри которой объявляем функцию, куда помещаем тело лямбды, и вызываем получившуюся функцию.

[assembly: Nemerle.Internal.OperatorAttribute ("NemerleExperiments", "~>", false, 145, 120)]

public macro @~>(name, body) {

def (fname, fargs) = UtilM.Expr2FunCall <| name;

def pargs = fargs.Map <| UtilM.Expr2Parm;

<[ fun(..$pargs) {

def $(fname: name)(..$pargs) $body;

$(fname: name)(..$fargs);

} ]>

}


Реализация, по-моему, короче описания на словах.
Именованная лямбда в действии
Именованная лямбда во всей красе! Вычисление факториала рекурсивным вызовов прямо на месте! Боюсь, на этом полезные применения этой конструкции заканчиваются. При этом, я думаю, если посмотреть получившийся код рефлектором или чем-то подобным, можно будет наблюдать заборы-коровники кучу классов для замыканий, возникших, по сути, на ровном месте. Полезность на уровне предыдущего пункта.


Класс UtilM в примерах мой, я недавно начал собирать туда всякие штуки, которые можно заподозрить в повторном использовании.
Макросы-операторы не обязательно должны представлять собой хаскиарт, можно давать нормальные полноценные имена, вроде with или extend. Надо было бы продемонстрировать, но редактировать написанное слишком лениво.
Извиняюсь за черный фон фрагментов кода, но я пытался переключиться на светлую тему студии, а у меня глаза вытекли.