Макрос для пересоздания структур
От: Алексей П Россия  
Дата: 22.08.07 07:12
Оценка: 129 (3)
Основываясь на прошлых попытках, уже ничего не предлагаю , а просто хочу поделиться удобным макросом, идея которого взята из 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 ()
    }
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.