Nemerle.Extensions1
От: Аноним  
Дата: 04.08.13 10:55
Оценка:
using Nemerle;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Console;

namespace Nemerle.Extensions1
{
  /// Implements Equals and related methods, using the concept of
  /// http://everything2.com/title/structural+equality
[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
  public macro StructuralEquality(tb : TypeBuilder, params _options : list[PExpr])
  {
    StructuralEqualityImpl.RunBeforeInheritance(tb);
  }

[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
  public macro StructuralEquality(tb : TypeBuilder, params options : list[PExpr])
  {
    StructuralEqualityImpl.RunWithTypedMembers(tb, options);
  }

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Field, Inherited = false, AllowMultiple = false)]
  public macro EqualsIgnore(tb : TypeBuilder, field : ParsedField)
  {
    StructuralEqualityImpl.Ignore(tb, field);
  }

[MacroUsage(MacroPhase.BeforeInheritance, MacroTargets.Property, Inherited = false, AllowMultiple = false)]
  public macro EqualsIgnore(tb : TypeBuilder, prop : ParsedProperty)
  {
    StructuralEqualityImpl.Ignore(tb, prop);
  }

  module StructuralEqualityImpl
  {
    public IndexOf[T](this source : list[T], elem : T) : int
    {
      def loop(source, elem, index)
      {
        match (source)
        {
          | head :: _ when head.Equals(elem) => index
          | _ :: tail => loop(tail, elem, index + 1)
          | _ => -1
        }
      }
      
      loop(source, elem, 0)
    }
    
    public NOfTypeRev[TIn, TFilter](this source : list[TIn]) : list[TFilter]
    {
      def loop(listIn : list[TIn], listOut : list[TFilter])
      {
        match (listIn)
        {
          | head :: tail =>
            match (head)
            {
              | x is TFilter => loop(tail, x :: listOut)
              | _ => loop(tail, listOut)
            }
            
          | _ => listOut
        }
      }
      
      loop(source, [])
    }
    
    // used as keys in UserData
    public IgnoredFieldsLabel : string = "StructuralEquality.IgnoredFields";
    public IgnoredPropertiesLabel : string = "StructuralEquality.IgnoredProperties";
    public IsEquatableImplementedLabel : string = "StructuralEquality.IsEquatableImplemented";
    public IsStructuralEquatableImplementedLabel : string = "StructuralEquality.IsStructuralEquatableImplemented";
    public StructuralEquatableTypeInfoLabel : string = "StructuralEquality.StructuralEquatableTypeInfoLabel";

    // implements interfaces
    public RunBeforeInheritance(tb : TypeBuilder) : void
    {
      def type = GetTypeName(tb);
      
      if (tb.IsValueType || tb.IsSealed)
        tb.Define(<[ decl:
          private EqualsImpl($("other" : dyn) : $type) : bool
          {
          } ]>);
      else
        tb.Define(<[ decl:
          protected virtual EqualsImpl($("other" : dyn) : $type) : bool
          {
          } ]>);
          
      when (tb.Ast is TopDeclaration.Variant)
        foreach (vo in tb.GetVariantOptions())
          vo.Ast.AddCustomAttribute(<[ StructuralEquality ]>);

      // Nemerle doesn't build if Tuples from stdlib are changed
      unless (tb.IsVariantOption)
      {
        tb.AddImplementedInterface(<[ System.IEquatable.[$type] ]>);
        tb.UserData.Add(IsEquatableImplementedLabel, true);

        // only .NET 4.0+ supports this
        when (tb.Manager.NameTree.LookupExactType("System.Collections.IStructuralEquatable") is Some(iface))
        {
          tb.AddImplementedInterface(<[ System.Collections.IStructuralEquatable ]>);
          tb.UserData.Add(IsStructuralEquatableImplementedLabel, true);
          tb.UserData.Add(StructuralEquatableTypeInfoLabel, iface);
        }
      }
    }

    // parses options, defines methods
    public RunWithTypedMembers(tb : TypeBuilder, options_expr : list[PExpr]) : void
    {
      //assert2(false);
      def options = SEOptions.Parse(options_expr);

      def get_relevant_fields(tb)
      {
        def all_fields = tb.GetFields(BindingFlags.Public %|
                                      BindingFlags.NonPublic %|
                                      BindingFlags.Instance %|
                                      BindingFlags.DeclaredOnly);

        // retrieve all ignored fields
        def ignore_fields =
          if (tb.UserData.Contains(IgnoredFieldsLabel))
            tb.UserData[IgnoredFieldsLabel] :> List[string]
          else
            List();

        // ignored properties
        when (tb.UserData.Contains(IgnoredPropertiesLabel))
        {
          def prop_list = tb.UserData[IgnoredPropertiesLabel] :> List[string];

          foreach (prop in prop_list)
          {
            match (tb.LookupMember(prop).Find(x => x is IProperty))
            {
              | Some(builder is PropertyBuilder) =>
                match (builder.AutoPropertyField)
                {
                  | Some(field) => ignore_fields.Add(field.Name)
                  | _ => Message.Warning(builder.Location, $"$prop is not an autoproperty. No need to use EqualsIgnore")
                }
                
              | _ => Message.Error($"Property $prop not found.")
            }
          }
        }

        ignore_fields.AddRange(options.IgnoreFields);
        ignore_fields.Sort();

        // remove ignored fields and return result
        all_fields.Filter(x => ignore_fields.BinarySearch(x.Name) < 0);
      }

      // fields that are not ignored when evaluating structural equality
      def relevant_fields = get_relevant_fields(tb);

      // true if strict type equality is needed, i. e. no subtypes are allowed;
      def typecheck_needed = !tb.IsSealed && !tb.IsVariantOption && !tb.IsValueType && options.CheckTypes;
      //assert2(false);
      DefineEquality(tb, relevant_fields, typecheck_needed, options.EmitDebugSources);
      DefineHashCode(tb, relevant_fields, options.EmitDebugSources);
      DefineOperators(tb);
      DefineStructural(tb);
    }

    // adds a field to ignore list
    public Ignore(tb : TypeBuilder, field : ClassMember.Field) : void
    {
        unless (tb.UserData.Contains(IgnoredFieldsLabel))
          tb.UserData.Add(IgnoredFieldsLabel, List.[string]());

        def lst = tb.UserData[IgnoredFieldsLabel] :> List[string];
        unless (lst.Contains(field.Name))
          lst.Add(field.Name);
    }

    // adds a property to ignore list
    public Ignore(tb : TypeBuilder, prop : ClassMember.Property) : void
    {
        unless (tb.UserData.Contains(IgnoredPropertiesLabel)) tb.UserData.Add(IgnoredPropertiesLabel, List.[string]());

        def lst = tb.UserData[IgnoredPropertiesLabel] :> List[string];
        unless (lst.Contains(prop.Name)) lst.Add(prop.Name);
    }

    // represents macro options
[Record]
    struct SEOptions
    {
      public IgnoreFields : list[string] { get }
      public CheckTypes : bool { get }
      public EmitDebugSources : bool { get }

      public static Default : SEOptions { get; default SEOptions([], true, true) }

      public static Parse(options : list[PExpr]) : SEOptions
      {
        mutable check_types = true;
        mutable ignore_fields = [];
        mutable emitDebugSources = true;

        foreach (opt in options)
        {
          | <[ CheckTypes = true ]> => check_types = true;
          | <[ CheckTypes = false ]> => check_types = false;
          | <[ Ignore = [..$flds] ]>
          | <[ Ignore = $fld ]> with flds = [fld] =>

          // add field names as strings
          ignore_fields += flds.MapFiltered(_ is PExpr.Ref, x => (x :> PExpr.Ref).name.Id)
          
          | <[ EmitDebugSources = true ]> => emitDebugSources = true;
          | <[ EmitDebugSources = false ]> => emitDebugSources = false;

          | _ => Message.Error("Unknown options for StructuralEquality.")
        }
        
        SEOptions(ignore_fields, check_types, emitDebugSources)
      }
    }
    
    GetEqualsImpl(typeInfoToLookup : TypeInfo, paramType : FixedType.Class) : option[IMethod]
    {
      def baseEqualsImpl = typeInfoToLookup
        .LookupMember("EqualsImpl")
        .NOfTypeRev.[_, IMethod]()
        .Find(m => m.Header.Parameters.Length == 1 && m.Header.Parameters.Head.Type.Equals(paramType));
          
      baseEqualsImpl
    }
    
    IsParentImplement_EqualsImpl(tb : TypeBuilder) : bool
    {
      GetEqualsImpl(tb.BaseType, tb.BaseClass).IsSome
    }
    
    HasTypedEquals(type : FixedType) : bool
    {
      | Class(ti, _) =>
        def result = ti
          .LookupMember("Equals")
          .NOfTypeRev.[_, IMethod]()
          .Find(m => m.Header.Parameters.Length == 1 && m.Header.Parameters.Head.Type.Equals(type));
      
        result.IsSome
        
      | _ => false
    }
    
    DefineEquality(tb : TypeBuilder, fields : Seq[IField], _check_types : bool, emitDebugSources : bool) : void
    {
      assert2(tb.BaseType.LookupMemberAvailable);
      def defineMember(member : ClassMember) : void
      {
        if (emitDebugSources) _ = tb.DefineWithSource(member)
        else tb.Define(member);
      }

      // generates comparison code for a single field
      def invokeEquals(x : IField)
      {
        def type = x.GetMemType();
        def value = <[ $(x.Name : usesite) ]>;
        
        if (type.IsPrimitive)
          <[ this.$value == other.$value ]> // primitive magic
        else if (type.Equals(tb.InternalType.String))
          <[ string.Equals(this.$value, other.$value) ]>
        else if (type.IsValueType && !type.CanBeNull && HasTypedEquals(type))
            <[ this.$value.Equals(other.$value) ]> // no null-checks
            // <[ if ($value.HasValue) other.$value.HasValue && this.$value.Value.Equals(other.$value.Value); else !other.$value.HasValue; ]> // For T?
        //else if (type is FixedType.StaticTypeVarRef) // for type parameters
        // <[ EqualityComparer.Default.Equals($value, other.$value) ]>
        else
          <[ EqualityComparer.Default.Equals(this.$value, other.$value) ]>;
      }

      def type = GetTypeName(tb);
      //def type_checker =
      // if (check_types)
      // <[ other.GetType().Equals(this.GetType()) ]>
      // else
      // <[ true ]>;

      def isParentImplement_EqualsImpl = IsParentImplement_EqualsImpl(tb);
      def parentType = tb.BaseClass;
      def baseCall =
        if (isParentImplement_EqualsImpl)
          <[ base.EqualsImpl(other : $(parentType : typed)) ]>
        else
          <[ true ]>;
          
      // core comparison code (type checker + comparison for each field)
      def body = fields.Fold(baseCall, (f, acc) => <[ $(invokeEquals(f)) && $acc ]> );

      // no null-check for structs
      def fun_body = if (tb.GetMemType().CanBeNull) <[ match (other) { | null => false | _ => $body } ]> else body;
      def fun_body = <[
_ = other; // shut the compiler up if body degrades to "true"
            $fun_body
        ]>;

      match (GetEqualsImpl(tb, tb.GetMemType()))
      {
        | Some(method is MethodBuilder) =>
          method.Body = fun_body;
          //assert2(false);
          method.Ast.Body = fun_body;
          when (emitDebugSources)
            tb.TyManager.GenerateFakeSourceCode(tb, method.Ast);
          
        | _ => Message.FatalError($"Can't find EqualsImpl(other : $(tb.GetMemType())) in $tb");
      }
  
      def implementsEquatable = AskUserData(tb, IsEquatableImplementedLabel);
      if (implementsEquatable)
        defineMember(<[ decl:
            public Equals(other : $type) : bool implements System.IEquatable.[$type].Equals
            {
              EqualsImpl(other)
            } ]>);
      else
        defineMember(<[ decl:
            public Equals(other : $type) : bool
            {
              EqualsImpl(other)
            } ]>);
          
      when (isParentImplement_EqualsImpl)
        defineMember(<[ decl:
          protected override EqualsImpl(other : $(parentType : typed)) : bool
          {
            | x is $type => EqualsImpl(x)
            | _ => false
          }
        ]>);
      
      // implements object.Equals
      defineMember(<[ decl:
        public override Equals(other : System.Object) : bool
        {
          | x is $type => EqualsImpl(x)
          | _ => false
        }
      ]>);
    }

      DefineHashCode(tb : TypeBuilder, fields : Seq[IField], emitDebugSources : bool) : void
    {
      def callBase = IsParentImplement_EqualsImpl(tb);
      
      def (countConst, hash_bodyConst, hash_bodyMutable) = fields.Fold((0,<[0x011C9DC5^0x80000000]>,<[_hashCode]>),
        fun ( f, (countConst, initExprConst, initExprMutable))
        {
  
 
          def type = f.GetMemType();
          def value = <[ $(f.Name : usesite) ]>;
 
          if(f.Name!="_hashCode")
          {
           
          
          def gethashcode =
             
          
              if (type.Equals(tb.InternalType.Int32))
                <[ $value ]>
              else if (type.Equals(tb.InternalType.UInt32))
               <[ $value :> int ]>
              else if (type.Equals(tb.InternalType.Boolean))
               <[ if($value) 0x959595C3C3 else 0xC953C953 ]>
               else 
              if (type.IsValueType)
               if (type.CanBeNull) <[if($value is null) 0x359CC953 else $value.GetHashCode()]> else <[$value.GetHashCode()]>
               else 
               if (type is FixedType.StaticTypeVarRef) <[ EqualityComparer.Default.GetHashCode($value) ]> else <[if($value is null) 0x359CC953 else $value.GetHashCode()]>;
              
               
               
            //else if (type.IsValueType)
            //  if (type.CanBeNull)
            //    <[ EqualityComparer.Default.GetHashCode($value) ]> // можно оптимизировать
            //  else
            //    <[ this.$value.GetHashCode() ]>
            //else if (type is FixedType.StaticTypeVarRef)
            //  <[ EqualityComparer.Default.GetHashCode($value) ]>
            //else
            //  <[ this.$value?.GetHashCode() ]>;
         if(!f.IsMutable&(type.IsPrimitive||type.Equals(tb.InternalType.String)))     
         (
          countConst+1,
         <[ (_*_) (0x01000193, (($initExprConst) ^ ($gethashcode)) )]>,
         <[$initExprMutable]>

           )else
         (
          countConst,
         <[ $initExprConst]>,
         <[ (_*_) (0x01000193, (($initExprMutable) ^ ($gethashcode)) )]>

           );       
           
       } else
        (
          countConst,
         <[ $initExprConst]>,
         <[ $initExprMutable]>

           );       
       
        });
        
          
      def variantOptionHash =
        if (tb.IsVariantOption)
        {
          def optionNumber = tb.GetVariantOptionParent().GetVariantOptions().IndexOf(tb) + 1;
          assert(optionNumber > 0);
          <[  $(optionNumber : int); ]>
        }
        else
          <[ 0 ]>;
          
      def baseHash =
        if (callBase)
          <[ base.GetHashCode(); ]>
        else
          <[ 0 ]>;

      def body_hashcode =

          <[
          unchecked
            {
              when(_hashCode==0) _hashCode= $hash_bodyConst;
         
              (_+_)(((_^_) ($hash_bodyMutable,$baseHash)),$variantOptionHash) : int;

            }
          ]>;

      def body =

          <[
          unchecked
            {
              def _hashCode= $hash_bodyConst;
              (_+_)(((_^_) ($hash_bodyMutable,$baseHash)),$variantOptionHash) : int;

            }
          ]>;

          
      def define(expr)
      {
        if (emitDebugSources) _ = tb.DefineWithSource(expr)
        else tb.Define(expr)
      }
      if(fields.Exists((t)=>{t.Name=="_hashCode"})||countConst>=7)
      {
      define(<[ decl: public mutable _hashCode : int; ]> );
      define(<[ decl: public override GetHashCode() : int { $body_hashcode } ]>);
  }else
  {
      define(<[ decl: public override GetHashCode() : int { $body } ]>);
  
  }
  
  
    }

    DefineOperators(tb : TypeBuilder) : void
    {
      def type = GetTypeName(tb);

      if (tb.IsValueType)
      {
        tb.Define(<[ decl:
          public static @==(first : $type, second : $type) : bool
          {
            first.Equals(second)
          }
        ]>);
      }
      else
      {
        tb.Define(<[ decl:
          public static @==(first : $type, second : $type) : bool
          {
            if (first is null) second is null else first.Equals(second)
          }
        ]>);
      }

      tb.Define(<[ decl:
        public static @!= (first : $type, second : $type) : bool
        {
          !(first == second)
        }
      ]>);
    }

    DefineStructural(tb : TypeBuilder) : void
    {
      when (AskUserData(tb, IsStructuralEquatableImplementedLabel))
      unless (tb.BaseType?.IsDerivedFrom(tb.UserData[StructuralEquatableTypeInfoLabel] :> TypeInfo))
      {
        tb.Define(<[ decl:
          public Equals(other : object, _comparer : System.Collections.IEqualityComparer) : bool
          {
            Equals(other);
          }
        ]>);

        tb.Define(<[ decl:
          public GetHashCode(_comparer : System.Collections.IEqualityComparer) : int
          {
            GetHashCode();
          }
        ]>);
      }
    }

    //MarkCompilerGenerated(cm : ClassMember) : ClassMember
    //{
    // cm.AddCustomAttribute(<[System.Runtime.CompilerServices.CompilerGenerated]>);
    // cm
    //}

    // no api to get type name with params; 'this' keyword in this context is bugged
    GetTypeName(tb : TypeBuilder) : PExpr
    {
      //<[ $(tb.GetMemType() : typed) ]>
      def splicable_to_ref(s : Splicable)
      {
        | Name(n)
        | HalfId(n) => PExpr.Ref(n)
        | Expression(e) => e
      }

      def qname = PExpr.FromQualifiedIdentifier(tb.Manager, tb.Ast.FullQualifiedName);
      if (tb.Ast.TypeParameters is null)
        <[ $qname ]>
      else
      {
        def args = tb.Ast.TypeParameters.tyvars.Map(splicable_to_ref);
        <[ $qname.[..$args] ]>
      }
    }

    AskUserData(tb : TypeBuilder, question : string, defaultAnswer : bool = false) : bool
    {
      if (!tb.UserData.Contains(question)) defaultAnswer else tb.UserData[question] :> bool
    }
  }
}
Re: Nemerle.Extensions1
От: Аноним  
Дата: 06.08.13 20:29
Оценка:
Может есть смысл попробывать данный вариант.
Re[2]: Nemerle.Extensions1
От: catbert  
Дата: 07.08.13 15:43
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Может есть смысл попробывать данный вариант.


Попробуйте оформить это как pull-request в гитхабе.
Но сразу в неймспейс Nemerle.Extensions.

Разработчикам языка намного проще принять пулл-реквест, поэтому намного выше вероятность каких-то действий.
Re: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 07.08.13 16:05
Оценка:
Здравствуйте, Аноним, Вы писали:

Было бы очень хорошим ходом разъяснить что этот код делает и какую проблему решает.
Ну и в самом коде комментарии тоже были бы полезны.

P.S.
IndexOf и NOfTypeRev: А разве нет уже похожих функций в стандартной библиотеке языка ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 08.08.13 07:13
Оценка:
Здравствуйте, Аноним, Вы писали:

Кстати, я бы хотел от макроса StructuralEquality следующее:
1. Поддержка variant
2. Возможность вынесения в отдельные классы без затрагивания оригинальных.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: Nemerle.Extensions1
От: catbert  
Дата: 08.08.13 10:12
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>2. Возможность вынесения в отдельные классы без затрагивания оригинальных.


Это как?
Re[3]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 08.08.13 12:04
Оценка:
Здравствуйте, catbert, Вы писали:

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


_NN>>2. Возможность вынесения в отдельные классы без затрагивания оригинальных.


C>Это как?


Ну скажем пример:
variant A { ... }

/*
 Создаст 
namespace AA
{
 class AEqualityComparer : System.Collections.Generic.EqualityComparer[A]
 {
   GetHashCode()...
   Equals(...)..
 }
}
 */
[assembly: StructuralEquality(A, Namespace = "AA")]
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: Nemerle.Extensions1
От: Аноним  
Дата: 08.08.13 12:43
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Здравствуйте, Аноним, Вы писали:


_NN>Кстати, я бы хотел от макроса StructuralEquality следующее:

_NN>1. Поддержка variant
_NN>2. Возможность вынесения в отдельные классы без затрагивания оригинальных.


1) Поддержка вариант там есть.
2) Что бы понять не хватает данных.
Re[3]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 08.08.13 12:56
Оценка:
Здравствуйте, Аноним, Вы писали:

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


_NN>>Здравствуйте, Аноним, Вы писали:


_NN>>Кстати, я бы хотел от макроса StructuralEquality следующее:

_NN>>1. Поддержка variant
_NN>>2. Возможность вынесения в отдельные классы без затрагивания оригинальных.


А>1) Поддержка вариант там есть.

А есть также поддержка Include, Exclude как в оригинальном макросе ?

А>2) Что бы понять не хватает данных.

Ну я в соседнем сообщении описал.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Nemerle.Extensions1
От: Аноним  
Дата: 08.08.13 13:07
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Здравствуйте, Аноним, Вы писали:


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


_NN>>>Здравствуйте, Аноним, Вы писали:


_NN>>>Кстати, я бы хотел от макроса StructuralEquality следующее:

_NN>>>1. Поддержка variant
_NN>>>2. Возможность вынесения в отдельные классы без затрагивания оригинальных.


А>>1) Поддержка вариант там есть.

_NN>А есть также поддержка Include, Exclude как в оригинальном макросе ?

Это и есть оригинальный, только изменил генерацию хешкода на существенно более лучшую (использовал умножение) и немного оптимизировал генерируемый код. Кроме того добавил приват поле хешкода генерируемое если неизменяемых значений 7 и более.

А>>2) Что бы понять не хватает данных.

_NN>Ну я в соседнем сообщении описал.

Так я не понял. Размещение процедурн в другом неймспейсе? какой профит. Мне что бы понять не хватает знаний.
Re[5]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 08.08.13 13:18
Оценка:
Здравствуйте, Аноним, Вы писали:


А>>>1) Поддержка вариант там есть.

_NN>>А есть также поддержка Include, Exclude как в оригинальном макросе ?

А>Это и есть оригинальный, только изменил генерацию хешкода на существенно более лучшую (использовал умножение) и немного оптимизировал генерируемый код. Кроме того добавил приват поле хешкода генерируемое если неизменяемых значений 7 и более.


Ясно.
Тогда баг остался

А>>>2) Что бы понять не хватает данных.

_NN>>Ну я в соседнем сообщении описал.

А>Так я не понял. Размещение процедурн в другом неймспейсе? какой профит. Мне что бы понять не хватает знаний.


Дело в том что в некоторых случаях не хочется трогать оригинальный класс.
Более того возможны разные варианты сравнения, как это реализовать ?
Пример:
variant A
{
  | X { Name : string; Field : int; }
}

[assembly: StructuralEquality(A, Ignore = Name, Namespace = WithoutName)]
[assembly: StructuralEquality(A, Ignore = Field, Namespace = WithoutField)]
[assembly: StructuralEquality(A, Namespace = "Default")]


И тогда можно сравнивать именно так как хотим:
def a = A.X("a", 10);
def b = A.X("b", 10);

WithoutName.AEqualityComparer().Equals(a, b) == true
WithoutField.AEqualityComparer().Equals(a, b) == false
Default.AEqualityComparer().Equals(a, b) == false
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[6]: Nemerle.Extensions1
От: Аноним  
Дата: 08.08.13 16:06
Оценка:
http://files.mail.ru/B6BD1A6E524F40378F91A91537E343B0

Поправил баг.

[assembly: StructuralEquality(A, Namespace = "Default")]
не пойму как разбирать.
Re[7]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 08.08.13 18:40
Оценка:
Здравствуйте, Аноним, Вы писали:

А>http://files.mail.ru/B6BD1A6E524F40378F91A91537E343B0


А>Поправил баг.


А>[assembly: StructuralEquality(A, Namespace = "Default")]

А>не пойму как разбирать.

Также как и обычный макрос разбирает:
[MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro StructuralEquality(tb : TypeBuilder, params options : list[PExpr])
{
  StructuralEqualityImpl.RunWithTypedMembers(tb, options);
}

...
[Record]
    struct SEOptions
    {
      public IgnoreFields : list[string] { get }
      public CheckTypes : bool { get }
      public EmitDebugSources : bool { get }

      public static Default : SEOptions { get; default SEOptions([], true, true) }

      public static Parse(options : list[PExpr]) : SEOptions
      {
        mutable check_types = true;
        mutable ignore_fields = [];
        mutable emitDebugSources = true;

        foreach (opt in options)
        {
          | <[ CheckTypes = true ]> => check_types = true;
          | <[ CheckTypes = false ]> => check_types = false;
          | <[ Ignore = [..$flds] ]>
          | <[ Ignore = $fld ]> with flds = [fld] =>

          // add field names as strings
          ignore_fields += flds.MapFiltered(_ is PExpr.Ref, x => (x :> PExpr.Ref).name.Id)
          
          | <[ EmitDebugSources = true ]> => emitDebugSources = true;
          | <[ EmitDebugSources = false ]> => emitDebugSources = false;

          | _ => Message.Error("Unknown options for StructuralEquality.")
        }
        
        SEOptions(ignore_fields, check_types, emitDebugSources)
      }
    }


Выражение приходит в формате PExpr который сопоставляется с образцом как любой другой параметр.

Пример макроса уровня сборки: GenerateTypedJS.n.

Кстати, изменения лучше было бы всего оформлять в виде пул реквеста.
Сначала делаете форк на http://github.com/rsdn/nemerle
Потом добавляете изменения в ваш форк.
И там появляется кнопка 'Pull Request'.
Так можно следить и за историей изменений и легко их добавить в основной репозиторий.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[8]: Nemerle.Extensions1
От: Аноним  
Дата: 09.08.13 06:48
Оценка:
Здравствуйте, _NN_, Вы писали:

Не ясно как быть с хешкодом тогда.
Re[8]: Nemerle.Extensions1
От: Аноним  
Дата: 09.08.13 10:00
Оценка:
Здравствуйте, _NN_, Вы писали:

[StructuralEquality(Ignore = name, Ignore = other, Namespace="777")]
[StructuralEquality(Ignore = name, Ignore = other, Namespace="999")]
variant X
{
 | A { name : string;name2 : string; other : int; }
 | B
}


Такой код что ли?
он не работает
Re[9]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 09.08.13 16:54
Оценка:
Здравствуйте, Аноним, Вы писали:

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


А>Не ясно как быть с хешкодом тогда.


Хм.. согласен, тут надо это продумать хорошо.
Можно например в макросе указывать для типов чтобы пользоавться таким-то EqualityComparer-ом.

К примеру
[assembly: StructuralEquality(MyVariant, Namespace = M, 
  EqualityComparers = [(string , MyEqualityComparer), (int, MyIntComparer)])


А то что не перечислено будет пользоваться обычным obj.GetHashCode().


P.S.
Это предложение, код не рабочий.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[7]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 09.08.13 17:01
Оценка:
Здравствуйте, Аноним, Вы писали:

А>http://files.mail.ru/B6BD1A6E524F40378F91A91537E343B0


А>Поправил баг.


Есть пару мест в вашем коде закомментированных.
Не ясно это ошибка или преднамеренно.
Если преднамеренно, что лучше такие места вычистить , чтобы не было вопросов.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[8]: Nemerle.Extensions1
От: Аноним  
Дата: 09.08.13 18:45
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Здравствуйте, Аноним, Вы писали:


А>>http://files.mail.ru/B6BD1A6E524F40378F91A91537E343B0


А>>Поправил баг.


_NN>Есть пару мест в вашем коде закомментированных.

_NN>Не ясно это ошибка или преднамеренно.

_NN>Если преднамеренно, что лучше такие места вычистить , чтобы не было вопросов.

приучили старый код не удалять, а комментировать, чо бы было понятно откуда что растет.
Re[9]: Nemerle.Extensions1
От: _NN_ www.nemerleweb.com
Дата: 09.08.13 19:09
Оценка:
Здравствуйте, Аноним, Вы писали:

_NN>>Если преднамеренно, что лучше такие места вычистить , чтобы не было вопросов.

А>приучили старый код не удалять, а комментировать, чо бы было понятно откуда что растет.

Для этого есть система контроля версий: https://github.com/rsdn/nemerle/tree/StructuralEquality

Кстати, тут подумалось что возможно полезной функцией было бы возможное указание более полного типа в Ignore:
[StructuralEquality(Ignore = A.name)]
variant A
{
 | X { name : string; other : int; }
 | Y { name : string; }
}

A.X("a", 1) == A.X("b", 1)
A.Y("a") != A.Y("b")
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: Nemerle.Extensions1
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.08.13 19:35
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>Кстати, я бы хотел от макроса StructuralEquality следующее:

_NN>1. Поддержка variant

Она вроде как есть. Или ее сломали?

_NN>2. Возможность вынесения в отдельные классы без затрагивания оригинальных.


А смысл? Все равно они не могут применяться для внешних типов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.