Pretty print для variants
От: vaskir Россия vaskir.blogspot.com
Дата: 11.01.16 19:29
Оценка:
Существует ли стандартный способ переопределения ToString для вариантов, делающий что-то похожее, но автоматически?

variant Node {
    | Scal { v: Scalar }
    | List { children: list[Node] }
    | Map { nodes: list[string * Node] }
    public override ToString(): string {
        match (this) {
        | Scal(x) => $"Scalar($x))"
        | List(x) => $"List(..$x))"
        | Map(x) => $"Map(..$x))"
        }
    }
}
Re: Pretty print для variants
От: VladD2 Российская Империя www.nemerle.org
Дата: 11.01.16 21:14
Оценка:
Здравствуйте, vaskir, Вы писали:

V>Существует ли стандартный способ переопределения ToString для вариантов, делающий что-то похожее, но автоматически?


Нет, но можно довольно легко написать такую макру.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Pretty print для variants
От: vaskir Россия vaskir.blogspot.com
Дата: 12.01.16 13:52
Оценка:
VD>Нет, но можно довольно легко написать такую макру.

Написал. На первый взляд ничего страшного не видишь?

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

module ShowImpl {
    public Transform(ty: TypeBuilder): PExpr {
        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."); 
            <[]>
        | _ => {
            ty.GetVariantOptions().Iter(opt => {
                def fields: list[IField] = opt.GetFields(BindingFlags.Public %| BindingFlags.Instance %| BindingFlags.DeclaredOnly);
                Message.Hint($"Fields: ..$fields");
                def fieldsExpr: PExpr = fields.Fold(<[]>, (f, acc) => {
                     match (acc) {
                     | <[]> => <[ $(f.Name: usesite).ToString() ]>
                     | _ => <[ $acc + ", " + $(f.Name: usesite).ToString() ]>
                     }
                });
                
                def body: PExpr =
                    match (fieldsExpr) {
                    | <[]> => <[ $(opt.Name) ]>
                    | _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
                    };
                       
                opt.Define(<[decl:
                    public override ToString(): string {
                        $body
                    }
                    ]>);
            });
            
            <[]>
          }
        }
    }
}
Re[3]: Pretty print для variants
От: VladD2 Российская Империя www.nemerle.org
Дата: 12.01.16 23:02
Оценка: 4 (1)
Здравствуйте, vaskir, Вы писали:

V>Написал. На первый взляд ничего страшного не видишь?


В простых случаях должно работать.

Проблемы могут возникнуть в двух случаях:
1. Если пользователь объявил рукописный ToString(), будет сообщение об ошибке. Правильнее было бы проверять нет ли рукописного ToString() с генерацией ToString() только если его нет.
2. Более правильно было бы не печатать все поля текущего объекта, а печатать псевда-параметры конструкторов помеченных как RecordCtorAttribute. У таких конструкторов параметры размечаются атрибутами MappedMemberAttribute, где указывается реальное имя параметра. Посмотри через дкомпилятор как выглядит вариантный тип (это касается не только вариантных типов но и других видов записей). MappedMemberAttribute задает соответствие поля записи и параметра конструктора. Реализация использующая эти атрибуты будет более корректной. Но и твоя будет работать для 98% вариантных типов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Pretty print для variants
От: vaskir Россия vaskir.blogspot.com
Дата: 13.01.16 14:33
Оценка:
Всё исправил:

  Макрос
[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro Show(ty: TypeBuilder) {
    ShowImpl.Transform (ty)
}

module ShowImpl {
    GetRecordCtorParams(t: TypeBuilder): option[list[string]] {
        def recordCtor =
            t.GetConstructors(BindingFlags.Public)
             .FirstOrDefault(x => x.IsCustomAttributeDefined("Nemerle.Internal.RecordCtorAttribute"))
             .OfNull();
                    
        recordCtor.Map(x => {
            x.GetParameters()
             .Map(x => x.CustomAttributes)
             .Flatten()
             .Choose(x => 
                 match (x) {
                 | PExpr.Call(_, pars) =>
                     Some(pars.Choose(par =>
                         match (par) {
                         | PExpr.Literal(Literal.String(name)) => Some(name)
                         | _ => None()
                         }))
                 | _ => None()
                 })
             .Flatten()
          })
    }
    
    public Transform(ty: TypeBuilder): void {
        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."); 
        | _ => {
            ty.GetVariantOptions().Iter(opt => {
                unless (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
                {
                    match (GetRecordCtorParams(opt)) {
                    | None => ()
                    | Some(pars) => {
                        def fieldsExpr: PExpr = pars.Fold(<[]>, (f, acc) => {
                             match (acc) {
                             | <[]> => <[ $(f: usesite).ToString() ]>
                             | _ => <[ $acc + ", " + $(f: usesite).ToString() ]>
                             }
                        });
                
                        def body: PExpr =
                            match (fieldsExpr) {
                            | <[]> => <[ $(opt.Name) ]>
                            | _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
                            };
                       
                        opt.Define(<[decl:
                            public override ToString(): string {
                                $body
                            }
                            ]>);
                      }
                   }
                }
            });
          }
        }
    }
}


Два вопроса:

Оптимально ли так определять наличие атрибута (по строковому имени)?
То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.
Re[5]: Pretty print для variants
От: VladD2 Российская Империя www.nemerle.org
Дата: 14.01.16 02:59
Оценка: 4 (1)
Здравствуйте, 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
                    }
                    ]>);
            }
          }
      }
    }
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Отредактировано 14.01.2016 3:01 VladD2 . Предыдущая версия . Еще …
Отредактировано 14.01.2016 3:01 VladD2 . Предыдущая версия .
Re[6]: Pretty print для variants
От: vaskir Россия vaskir.blogspot.com
Дата: 14.01.16 10:27
Оценка:
VD> Ниже я привел отрефакторнный и поправленный вариант твоего макроса с комментариями. Надеюсь они будут полезны.

Спасибо! Узнал много нового.

Да, возникает желание использовать Option.map/bind/fill/ofNull/... и функции типа

choose: ('a -> 'b option) -> 'a list -> 'b list (map 'a в 'b option и фильтрация != None за один проход). Не нашел ничего похожего в Nemerle (MapFiltered сначала фильтрует, потом преобразует)

или tryPick: 'a list -> ('a -> 'b option) -> 'b option (мэппим элементы, пока не вернется Some(_), тогда прерываемся и возвращаем этот Some(_))

Вообще, похоже, функции преобразования коллекций в Nemerle заметно отличаются от таковых в FP языках. + намного более широкое использование императивных конструкций типа foreach, when, unless.

VD> // Просим компилятор отдать нам FixedType для известного типа и берем у него TypeInfo.

VD> def mappedMemberAttribute = <[ ttype: Nemerle.Internal.MappedMemberAttribute; ]>.TypeInfo;

FixedType — это .net тип, полученный по символу (Nemerle.Internal.MappedMemberAttribute в данном случае)?

VD> // "размтачиваем" его одним залпом. У нас должен быть ровно один параметр типа "строковый литерал".

VD> | Some((_, [<[ $(name : string) ]>])) => name

PExpr.Literal(Literal.String(name)) --> <[ $(name : string) ]>
Красиво

Спасибо за hint насчет foreach и when, действительно мощно.
Re[7]: Pretty print для variants
От: VladD2 Российская Империя www.nemerle.org
Дата: 14.01.16 16:05
Оценка: 4 (1)
Здравствуйте, vaskir, Вы писали:

V>Да, возникает желание использовать Option.map/bind/fill/ofNull/... и функции типа


Дело привычки. Я вот даже не знал, что делает этот ofNull. Пришлось по другому коду догадываться.

V>choose: ('a -> 'b option) -> 'a list -> 'b list (map 'a в 'b option и фильтрация != None за один проход). Не нашел ничего похожего в Nemerle (MapFiltered сначала фильтрует, потом преобразует)


V>или tryPick: 'a list -> ('a -> 'b option) -> 'b option (мэппим элементы, пока не вернется Some(_), тогда прерываемся и возвращаем этот Some(_))


V>Вообще, похоже, функции преобразования коллекций в Nemerle заметно отличаются от таковых в FP языках. + намного более широкое использование императивных конструкций типа foreach, when, unless.


В Немерле есть, что называется, стандартный джентельменский набор взятый из ML-я — Filter/Map/Fold/Rev/Iter. С помощью этих функций можно что угодно со списком сделать. Тот же choose просто превращается в — Map(...).Filter(...). Но здесь все это не особо нужно было.

VD>> // Просим компилятор отдать нам FixedType для известного типа и берем у него TypeInfo.

VD>> def mappedMemberAttribute = <[ ttype: Nemerle.Internal.MappedMemberAttribute; ]>.TypeInfo;

V>FixedType — это .net тип, полученный по символу (Nemerle.Internal.MappedMemberAttribute в данном случае)?


FixedType — это вариантный тип которым, внутри компилятора, представляется немерловый выведенный или указанный явно тип.

Цитата "ttype:" просто упрощает код производят связывание указанной строки и формирует по нему FixedType. В случае с конструированными дженериками (т.е. с дженериками которым в качестве параметра типа подставлен конекртный тип) это существенно упрощает код.

V>PExpr.Literal(Literal.String(name)) --> <[ $(name : string) ]>

V>Красиво

Квази-цитирование. Что приятно, работает в обе стороны. Плюс в сплайсах могут быть не только литералы, но и, например, имена переменных.

V>Спасибо за hint насчет foreach и when, действительно мощно.


Это еще самый простой случай. В Немерле и foreach, и when с if поддерживают полноценный паттерн-матчинг. Так что можно писать, например, так:
foriach (x is Какой_то_Паттерн(с_переменной) in lst)
  Foo(с_переменной);

или так:
foreach (x in lst)
{
 | Какой_то_Паттерн(с_переменной) => Foo(с_переменной);
 | Другой_паттерн => ()
 | _ => ДействиеПоУмолчанию();
}

а when — это всего лишь гуард, который может идти за местом где паттерн идет.
Тоже самое в when:
class A {}
class B : A {}
...
def a : A = B();
when (a is B as b)
{
  Foo(b); // b имеет тип B
}

Причем foreach, when и if — это всего лишь макросы, которые переписывают код в match-и и рекурсивные вызовы. В самом языке ни циклов, ни управляющих конструкций вроде match нет. Все реализуется через рекурсию и match.

Кстати, when и if паттернматчингу обучил я лично, задолбавшийсь каждый раз переходить на более громоздкий match когда требовался паттерн-матчинг.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Pretty print для variants
От: WolfHound  
Дата: 14.01.16 16:35
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Кстати, when и if паттернматчингу обучил я лично, задолбавшийсь каждый раз переходить на более громоздкий match когда требовался паттерн-матчинг.

if обучил я. А ты долго боялся, что компиляция замедлится.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.