Основываясь на прошлых попытках, уже ничего не предлагаю
, а просто хочу поделиться удобным макросом, идея которого взята из Haskell-а.
Допустим, есть класс
[Record]
class A {
[Accessor] x: int;
[Accessor] y: string;
[Accessor] z: float;
}
и нужно создать экземпляр этого класса, основываясь на каком-то имеющемся, но с измененными полями. Понятно, что это можно сделать и вручную:
def a = A(1, "abc", 2.5f);
def b = A(a.X, "def", 1.4f);
Но если в классе не 3 поля, а 7-10, и делать это надо много раз, да еще и список полей может меняться, то это неудобно. Поэтому я написал макрос, который это автоматизирует, вот этот код эквивалентен второй строчке кода выше:
def b = RecreateRecord(a, y = "def", z = 1.4f);
Макрос выводит тип a, ищет подходящий конструктор и подменяет нужные параметры. Всё просто.
Если это кому-то интересно, вот код этого макроса, пользуйтесь как угодно:
using System;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle;
using Nemerle.Collections;
using Nemerle.Utility;
macro RecreateRecord(source, params assignments: list[PExpr])
{
// пытаемся вывести тип первого параметра - того, что надо пересоздать
match(Macros.ImplicitCTX().TypeExpr(source).ty.Fix())
{
| Class(tyinfo, _) as source_mtype =>
def properties = $[ mem | mem is IProperty in tyinfo.GetMembers(BindingFlags.Public)];
// проверить, существуют ли свойства с именами, такими же,
// как у параметров конструктора, либо преобразованными по правилу Accessor'а
def ctor_fits(ctor)
{
def parms = ctor.GetHeader().parms;
!parms.IsEmpty && parms.ForAll(parm =>
properties.Exists(prop =>
prop.Name == parm.Name || prop.Name == AccessorHelper.TransformName(parm.Name)
)
)
}
match(tyinfo.GetConstructors(BindingFlags.Public).Find(ctor_fits))
{
| None =>
Message.Error($"no suitable constructor found in type $(tyinfo.Name)");
source
| Some(ctor) =>
def ctor_typed = TExpr.StaticRef(source_mtype, ctor, []);
def ctor_parms = ctor.GetHeader().parms;
// список пар имя -> значение для параметров макроса
def new_values = assignments.Map(fun(expr) {
| <[ $name = $value ]> =>
def name = name.ToString();
unless(ctor_parms.Exists(parm => parm.Name == name))
Message.FatalError($"the constructor doesn't have a parameter named $name");
(name, value)
| _ => Message.FatalError($"expected list of param = value, got $expr")
});
// выражения для параметров конструктора, уже с заменами из параметров макроса
def new_parms = ctor_parms.Map(parm =>
match(List.Assoc(new_values, parm.Name))
{
| Some(value) => value
| None =>
def (Some(prop)) = properties.Find(prop =>
prop.Name == parm.Name || prop.Name == AccessorHelper.TransformName(parm.Name)
);
<[ source_ref.$(prop.Name : dyn) ]>
}
);
// результат
<[
def source_ref = $source;
$(ctor_typed : typed)(..$new_parms)
]>
}
| other_type =>
Message.Error($"the recreating expression must be a class, but it is $other_type");
source
}
}
// Это из макроса Accessor из стандартной библиотеки Nemerle.
// Вообще-то, у меня тут было больше методов, но для этого макроса необходим только этот.
public module AccessorHelper
{
public TransformName(name: string): string
{
def sb = StringBuilder ();
mutable next_upper = true;
foreach (ch in name)
if (ch == '_') {
next_upper = true;
} else if (next_upper) {
_ = sb.Append (char.ToUpper (ch));
next_upper = false;
} else
_ = sb.Append (ch);
sb.ToString ()
}
}