Реализация универсального равенства - PullRequest
3 голосов
/ 09 сентября 2011

У меня есть несколько общих функций равенства, которые используются при переопределении Object.Equals:

type IEqualityComparer<'T> = System.Collections.Generic.IEqualityComparer<'T>

let equalIf f (x:'T) (y:obj) =
  if obj.ReferenceEquals(x, y) then true
  else
    match box x, y with
    | null, _ | _, null -> false
    | _, (:? 'T as y) -> f x y
    | _ -> false

let equalByWithComparer (comparer:IEqualityComparer<_>) f (x:'T) (y:obj) = 
  (x, y) ||> equalIf (fun x y -> comparer.Equals(f x, f y))

Типичное использование будет:

type A(name) =
  member __.Name = name
  override this.Equals(that) = 
    (this, that) ||> equalByWithComparer StringComparer.InvariantCultureIgnoreCase (fun a -> a.Name)

type B(parent:A, name) =
  member __.Parent = parent
  member __.Name = name
  override this.Equals(that) = (this, that) ||> equalIf (fun x y ->
    x.Parent.Equals(y.Parent) && StringComparer.InvariantCultureIgnoreCase.Equals(x.Name, y.Name))

Я в основном доволен этим.Это уменьшает шаблон [wikipedia] .Но меня раздражает необходимость использовать equalBy вместо более краткого equalByWithComparer в типе B (так как его равенство зависит от родительского).

Такое ощущение, что должна быть возможность написать функцию, которая принимает ссылку на родителя (или проекции 0..N), которые проверяются на равенство с использованием Equals, вместе со свойством, которое нужно проверятьи сопутствующий компаратор, но я пока не смог представить его реализацию.Возможно, все это перестаралось (не уверен).Как может быть реализована такая функция?

РЕДАКТИРОВАТЬ

Основываясь на ответе Брайана, я пришел с этим, который, кажется, работает нормально.

let equalByProjection proj (comparer:IEqualityComparer<_>) f (x:'T) (y:obj) = 
  (x, y) ||> equalIf (fun x y -> 
    Seq.zip (proj x) (proj y)
    |> Seq.forall obj.Equals && comparer.Equals(f x, f y))

type B(parent:A, otherType, name) =
  member __.Parent = parent
  member __.OtherType = otherType //Equals is overridden
  member __.Name = name
  override this.Equals(that) = 
    (this, that) ||> equalByProjection
      (fun x -> [box x.Parent; box x.OtherType])
      StringComparer.InvariantCultureIgnoreCase (fun b -> b.Name)

Ответы [ 3 ]

2 голосов
/ 10 сентября 2011

Другая реализация, основанная на предложении Брайана:

open System
open System.Collections.Generic

// first arg is always 'this' so assuming that it cannot be null
let rec equals(a : 'T, b : obj) comparisons = 
    if obj.ReferenceEquals(a, b) then true
    else 
        match b with
        | null -> false
        | (:? 'T as b) -> comparisons |> Seq.forall(fun c -> c a b)
        | _ -> false

// get values and compares them using obj.Equals 
//(deals with nulls in both positions then calls <first arg>.Equals(<second arg>))
let Eq f a b = obj.Equals(f a, f b) 
// get values and compares them using IEqualityComparer
let (=>) f (c : IEqualityComparer<_>) a b = c.Equals(f a, f b)

type A(name) =
  member __.Name = name
  override this.Equals(that) = 
    equals (this, that) [
        (fun x -> x.Name) => StringComparer.InvariantCultureIgnoreCase
        ]

type B(parent:A, name) =
  member __.Parent = parent
  member __.Name = name
  override this.Equals(that) = 
    equals(this, that) [
        Eq(fun x -> x.Parent)
        (fun x -> x.Name) => StringComparer.InvariantCultureIgnoreCase
    ]
2 голосов
/ 09 сентября 2011

Вы просто ищете что-то, например,

[
    (fun x -> x.Parent), (fun a b -> a.Equals(b))
    (fun x -> x.Name), (fun a b -> SC.ICIC.Equals(a,b))
]

, где у вас есть список (проекция х компаратор) для запуска на объекте?(Возможно, потребуется больше аннотаций типов или умная конвейерная обработка.)

0 голосов
/ 10 сентября 2011

Просто чтобы удовлетворить любопытство Даниила, вот как кодировать экзистенциальный тип

exists 'p. ('t -> 'p) * ('p -> 'p -> bool)

в F #.Пожалуйста, не голосуйте за этот ответ!Слишком уродливо рекомендовать на практике.

Основная идея заключается в том, что приведенный выше экзистенциальный тип примерно эквивалентен

forall 'x. (forall 'p. ('t -> 'p) * ('p -> 'p -> bool) -> 'x) -> 'x

, потому что единственный способ реализовать значение этого типа - этоесли у нас действительно есть экземпляр ('t -> 'p) * ('p -> 'p -> bool) для некоторого 'p, который мы можем передать первому аргументу, чтобы получить возвращаемое значение произвольного типа 'x.

Хотя это выглядит сложнее, чемИсходный тип, этот последний тип может быть выражен в F # (через пару номинальных типов, по одному для каждого forall):

type ProjCheckerUser<'t,'x> =
    abstract Use : ('t -> 'p) * ('p -> 'p -> bool) -> 'x
type ExistsProjChecker<'t> =
    abstract Apply : ProjCheckerUser<'t,'x> -> 'x

// same as before
let equalIf f (x:'T) (y:obj) =               
    if obj.ReferenceEquals(x, y) then true               
    else               
    match box x, y with               
    | null, _ | _, null -> false               
    | _, (:? 'T as y) -> f x y               
    | _ -> false      

let checkAll (l:ExistsProjChecker<_> list) a b =
    // with language support, this could look more like:
    // let checkProj (ExistsProjChecker(proj,check)) = check (proj a) (proj b)
    // l |> List.forall checkProj
    let checkProj = {new ProjCheckerUser<_,_> with 
                        member __.Use(proj,check) = check (proj a) (proj b) }
    l |> List.forall 
            (fun ex -> ex.Apply checkProj)

let fastIntCheck (i:int) j = (i = j)
let fastStringCheck (s:string) t = (s = t)

type MyType(id:int, name:string) =
    static let checks = 
        // with language support this could look more like:
        // [ExistsProjChecker((fun (t:MyType) -> t.Id, fastIntCheck)
        //  ExistsProjChecker((fun (t:MyType) -> t.Name, fastStringCheck)]
        [{ new ExistsProjChecker<MyType> with 
               member __.Apply u = u.Use ((fun t -> t.Id), fastIntCheck)    }
         { new ExistsProjChecker<MyType> with 
               member __.Apply u = u.Use ((fun t -> t.Name), fastStringCheck) }]
    member x.Id = id
    member x.Name = name
    override x.Equals(y) =
        equalIf (checkAll checks) x y

Как видите, отсутствие языковой поддержки приводит к значительнымшаблон (в основном все выражения создания объекта, вызывает методы Use и Apply), что делает этот подход непривлекательным.

...