通用等于实现
我有几个通用的相等函数,它们在重写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]。但我很恼火必须在类型 B
中使用 equalBy
而不是更简洁的 equalByWithComparer
(因为它的相等性取决于其父级的相等性)。
感觉应该可以编写一个接受对父级(或 0..N 投影)的引用的函数,使用 Equals
检查父级(或 0..N 投影)的相等性,以及要检查的属性和它附带的比较器,但我还无法想象它的实现。也许这一切都太过分了(不确定)。这样的功能如何实现呢?
编辑
根据布莱恩的回答,我想出了这个,这似乎工作正常。
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)
I have several generic equality functions, which are used when overriding 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))
Typical usage would be:
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))
I'm mostly happy with this. It reduces boilerplate[wikipedia]. But I'm annoyed having to use equalBy
instead of the more concise equalByWithComparer
in type B
(since its equality depends on its parent's).
It feels like it should be possible to write a function that accepts a reference to the parent (or 0..N projections), which are checked for equality using Equals
, along with a property to be checked and its accompanying comparer, but I've yet been unable imagine its implementation. Perhaps all this is overdone (not sure). How might such a function be implemented?
EDIT
Based on Brian's answer, I came up with this, which seems to work okay.
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)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
另一个实现,基于 Brian 的建议:
Another implementation, based on Brian's suggestion:
您是否只是在寻找需要例如
在对象上运行(投影x比较器)列表的东西? (可能需要更多类型注释或巧妙的管道。)
Are you just looking for something that takes e.g.
where you have the list of (projection x comparer) to run on the object? (Probably will need more type annotations, or clever pipelining.)
为了满足大牛的好奇心,下面介绍如何在 F# 中对存在类型进行编码
。请不要对此答案投赞成票!在实践中推荐它太难看了。
基本思想是,上面的存在类型大致相当于
因为我们实现这种类型值的唯一方法是如果我们真的有一个
('t -> 'p) * ('p -> 'p -> bool)
对于某些'p
,我们可以将其传递给第一个参数以获取任意类型的返回值'x.
尽管它看起来比原始类型更复杂,但后一种类型可以用 F# 表示(通过一对名义类型,每个
forall
一个):如您所见,缺乏语言支持导致在很多样板文件中(基本上所有对象创建表达式,都调用方法
Use
和Apply
),这使得这种方法没有吸引力。Just to satisfy Daniel's curiosity, here's how to encode the existential type
in F#. Please don't up-vote this answer! It's too ugly to recommend in practice.
The basic idea is that the existential type above is roughly equivalent to
because the only way that we could implement a value of this type is if we really have an instance of
('t -> 'p) * ('p -> 'p -> bool)
for some'p
that we can pass to the first argument to get out a return value of the arbitrary type'x
.Although it looks more complicated than the original type, this latter type can be expressed in F# (via a pair of nominal types, one for each
forall
):As you can see, the lack of language support results in a lot of boilerplate (basically all of the object creation expressions, calls the the method
Use
andApply
), which makes this approach unattractive.