我有几个通用的相等函数,在覆盖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))
我对此非常满意。它减少了boilerplate[wikipedia]。但我很恼火,不得不使用equalBy
代替equalByWithComparer
类型中更简洁的B
(因为它的相等性取决于它的父级)。
感觉应该可以编写一个接受对父(或0..N投影)的引用的函数,使用Equals
检查它是否相等,以及要检查的属性和它伴随着比较器,但我还是无法想象它的实现。也许这一切都过头了(不确定)。如何实现这样的功能?
根据Brian的回答,我想出了这个,这似乎没问题。
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)
答案 0 :(得分:2)
您是否只是在寻找可能需要的东西,例如
[
(fun x -> x.Parent), (fun a b -> a.Equals(b))
(fun x -> x.Name), (fun a b -> SC.ICIC.Equals(a,b))
]
你在对象上运行的(投影x比较器)列表? (可能需要更多类型注释或巧妙的流水线操作。)
答案 1 :(得分:2)
另一个实现,基于Brian的建议:
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 :(得分:0)
为了满足丹尼尔的好奇心,这里是如何编码存在主义类型
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
),这使得这种方法没有吸引力