Информация об изменениях

Сообщение Re[5]: Pretty print для variants от 14.01.2016 2:59

Изменено 14.01.2016 3:01 VladD2

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

V>Оптимально ли так определять наличие атрибута (по строковому имени)?


Нормально. Другие пути сложнее.

V>То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))


Тут ты забыл проверить отсутствие аргументов. Твой макрос может ложно сработать на ToString с параметрами, а ToString-а без параметров может и не быть.

V>Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.


ОК, но есть более простые подходы. Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;

using System.Linq;

[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro Show(ty: TypeBuilder) {
    ShowImpl.Transform(Macros.ImplicitCTX(), ty)
}

module ShowImpl
{
    public Transform(typer : Typer, ty: TypeBuilder): void {
        // Функция мелкая и не имеет смысла за пределами данного метода. Делаем ее локальной!
        def getRecordCtorParams(tb: TypeBuilder): option[list[string]]
        {
          Macros.DefineCTX(typer); // нужно для "ttype:"
      
          // Просим компилятор отдать нам FixedType для известного типа и берем у него TypeInfo.
          def mappedMemberAttribute = <[ ttype: Nemerle.Internal.MappedMemberAttribute; ]>.TypeInfo;
          // Если в лямбде много букв, имеет смысл вынести ее в локальную функцию. Так читабельнее выходит.
          def extractFieldName(param)
          {
            // FindAttributeWithArgs находит нам нужны атрибут по его TypeInfo и отдает результат в виде 
            // Some(имя_атрибута, [параметр_атрибута1, параметр_атрибута2, ...]). 
            match (param.AttributesAndModifiers.FindAttributeWithArgs(mappedMemberAttribute, tb.GlobalEnv))
            {
              // "размтачиваем" его одним залпом. У нас должен быть ровно один параметр типа "строковый литерал".
              | Some((_, [<[ $(name : string) ]>])) => name
              | _ => assert(false) // Параметр должен быть железно! Если мы попали сюда, то что-то пошло не так :)
            }
          }

          tb.GetConstructors(BindingFlags.Public)
            // Find() возвращает результат завернутый в option[T]. Если уж ты решил поупражняться в ФП, то так удобнее буедт :).
            .Find(method => method.IsCustomAttributeDefined("Nemerle.Internal.RecordCtorAttribute"))
            // Ну, тут ты понял. Для тех кто не понял поясню, что первый Map() вызывается на option[Е].
            // Если в option есть значение, выполняется переданная функция и результат заворачивается в Some().
            // Иначе возвращается None().
            .Map(ctor => ctor.GetParameters().Map(extractFieldName))
        }

        match (ty)
        {
          | _ when ty.IsDelegate with n = "delegates"
          | _ when ty.IsEnum with n = "enums"
          | _ when ty.IsInterface with n = "interfaces"
          | _ when ty.IsModule with n = "modules" => 
              Message.Error(ty.Location, $"The Show macro does not support $n."); 
          | _ =>
            // Надо проверять наличие только ToString без параметров. Луже запихнуть эту проверку в локальную функцию, чтбы  код был понятнее.
            def existsHandmadeToString(opt) { opt.GetDirectMembers().OfType.[IMethod]().Any(x => x.Name == "ToString" && x.Header.Parameters.IsEmpty) }
          
            // Nemerle нормальный императивный язык. Iter() не нужны! Используем foreach :).
            // К тому же он в Nemerle крутой! Он паттарн-матчинг поддерживает. В данном случае используем его 
            // гуард для фильтрации типов в которых метод ToString написан вручную.
            foreach (opt when !existsHandmadeToString(opt) in ty.GetVariantOptions())
            {
               // Если match планируется обрабатывать только один случай паттерн-матчинга, то можно использовать макру when или if
              when (getRecordCtorParams(opt) is Some(pars))
              {
                def fieldsExpr = pars.Fold(<[]>, (f, acc) => {
                      match (acc) {
                      | <[]> => <[ $(f: usesite).ToString() ]>
                      | _ => <[ $acc + ", " + $(f: usesite).ToString() ]>
                      }
                });
                
                def body =
                    match (fieldsExpr) {
                    | <[]> => <[ $(opt.Name) ]>
                    | _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
                    };
                       
                opt.Define(<[decl:
                    public override ToString(): string {
                        $body
                    }
                    ]>);
            }
          }
      }
    }
}
Здравствуйте, vaskir, Вы писали:

V>Оптимально ли так определять наличие атрибута (по строковому имени)?


Нормально. Другие пути сложнее.

V>То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))


Тут ты забыл проверить отсутствие аргументов. Твой макрос может ложно сработать на ToString с параметрами, а ToString-а без параметров может и не быть.

V>Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.


ОК, но есть более простые подходы. Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.

using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;

using System.Linq;

[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro Show(ty: TypeBuilder) {
    ShowImpl.Transform(Macros.ImplicitCTX(), ty)
}

module ShowImpl
{
    public Transform(typer : Typer, ty: TypeBuilder): void {
        // Функция мелкая и не имеет смысла за пределами данного метода. Делаем ее локальной!
        def getRecordCtorParams(tb: TypeBuilder): option[list[string]]
        {
          Macros.DefineCTX(typer); // нужно для "ttype:"
      
          // Просим компилятор отдать нам FixedType для известного типа и берем у него TypeInfo.
          def mappedMemberAttribute = <[ ttype: Nemerle.Internal.MappedMemberAttribute; ]>.TypeInfo;
          // Если в лямбде много букв, имеет смысл вынести ее в локальную функцию. Так читабельнее выходит.
          def extractFieldName(param)
          {
            // FindAttributeWithArgs находит нужные атрибут по TypeInfo и отдает результат в виде 
            // Some(имя_атрибута, [параметр_атрибута1, параметр_атрибута2, ...]). 
            match (param.AttributesAndModifiers.FindAttributeWithArgs(mappedMemberAttribute, tb.GlobalEnv))
            {
              // "размтачиваем" его одним залпом. У нас должен быть ровно один параметр типа "строковый литерал".
              | Some((_, [<[ $(name : string) ]>])) => name
              | _ => assert(false) // Параметр должен быть железно! Если мы попали сюда, то что-то пошло не так :)
            }
          }

          tb.GetConstructors(BindingFlags.Public)
            // Find() возвращает результат завернутый в option[T]. Если уж ты решил поупражняться в ФП, то так удобнее буедт :).
            .Find(method => method.IsCustomAttributeDefined("Nemerle.Internal.RecordCtorAttribute"))
            // Ну, тут ты понял. Для тех кто не понял поясню, что первый Map() вызывается на option[Е].
            // Если в option есть значение, выполняется переданная функция и результат заворачивается в Some().
            // Иначе возвращается None().
            .Map(ctor => ctor.GetParameters().Map(extractFieldName))
        }

        match (ty)
        {
          | _ when ty.IsDelegate with n = "delegates"
          | _ when ty.IsEnum with n = "enums"
          | _ when ty.IsInterface with n = "interfaces"
          | _ when ty.IsModule with n = "modules" => 
              Message.Error(ty.Location, $"The Show macro does not support $n."); 
          | _ =>
            // Надо проверять наличие только ToString без параметров. Луже запихнуть эту проверку в локальную функцию, чтбы  код был понятнее.
            def existsHandmadeToString(opt) { opt.GetDirectMembers().OfType.[IMethod]().Any(x => x.Name == "ToString" && x.Header.Parameters.IsEmpty) }
          
            // Nemerle нормальный императивный язык. Iter() не нужны! Используем foreach :).
            // К тому же он в Nemerle крутой! Он паттарн-матчинг поддерживает. В данном случае используем его 
            // гуард для фильтрации типов в которых метод ToString написан вручную.
            foreach (opt when !existsHandmadeToString(opt) in ty.GetVariantOptions())
            {
               // Если match планируется обрабатывать только один случай паттерн-матчинга, то можно использовать макру when или if
              when (getRecordCtorParams(opt) is Some(pars))
              {
                def fieldsExpr = pars.Fold(<[]>, (f, acc) => {
                      match (acc) {
                      | <[]> => <[ $(f: usesite).ToString() ]>
                      | _ => <[ $acc + ", " + $(f: usesite).ToString() ]>
                      }
                });
                
                def body =
                    match (fieldsExpr) {
                    | <[]> => <[ $(opt.Name) ]>
                    | _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
                    };
                       
                opt.Define(<[decl:
                    public override ToString(): string {
                        $body
                    }
                    ]>);
            }
          }
      }
    }
}