具有函数case的并集类型上的F#等式行为

具有函数case的并集类型上的F#等式行为,f#,equality,discriminated-union,F#,Equality,Discriminated Union,我试图理解这种平等行为。记录相等性测试失败,但记录唯一属性的相等性测试通过。这是虫子吗?或者有人能解释这种行为吗 type TestUnion = | Case1 | Case2 of (int -> string) type TestType = { Foo : TestUnion } open Microsoft.VisualStudio.TestTools.UnitTesting [<TestClass>] publi

我试图理解这种平等行为。记录相等性测试失败,但记录唯一属性的相等性测试通过。这是虫子吗?或者有人能解释这种行为吗

type TestUnion =
    | Case1
    | Case2 of (int -> string)

type TestType =
    {
        Foo : TestUnion
    }

open Microsoft.VisualStudio.TestTools.UnitTesting

[<TestClass>]
public Testing() =

    let a = { Foo = Case1 }
    let b = { Foo = Case1 }

    [<TestMethod>]
    member __.ThisFails () =
        Assert.AreEqual(a, b)

    [<TestMethod>]
    member __.ThisPasses () =
        Assert.AreEqual(a.Foo, b.Foo)
类型TestUnion=
|案例1
|案例2(整数->字符串)
类型TestType=
{
Foo:TestUnion
}
打开Microsoft.VisualStudio.TestTools.UnitTesting
[]
公共测试()=
设a={Foo=Case1}
设b={Foo=Case1}
[]
成员“”失败()=
断言.AreEqual(a,b)
[]
成员_uuu.ThisPasses()=
Assert.AreEqual(a.Foo,b.Foo)
我知道它失败的原因是因为其中一个例子是函数。如果我将其更改为简单值,则两个测试都通过。但我感到奇怪的是,a)等式根本失败,因为使用了没有值的简单情况;b)记录等式失败,而属性等式通过


注意:如果还存在其他简单属性,则记录相等性将失败。这样,即使union类型属性测试为相等,union类型也会破坏整个记录的相等性。

如果您尝试直接比较
a=b
,则应该会看到此错误:

错误FS0001:类型“TestType”不支持“相等”约束,因为它是一个记录、联合或结构,具有一个或多个不支持“相等”约束的结构元素类型。避免在此类型中使用相等,或向该类型添加“StructuralEquality”属性以确定哪个字段类型不支持相等

函数值不支持F#中的相等,因此在这样的类型上不能有结构相等。我怀疑
Assert.AreEqual
调用不依赖于F#的结构相等比较

let areEqual x y = obj.Equals(x, y)
> areEqual a.Foo b.Foo;;
val it : bool = true

Assert.AreEqual
方法试图变得聪明,当然失败了。给定两个对象,此方法要做的第一件事是测试引用相等性:
obj.ReferenceEquals(Case1,Case1)
。这会立即起作用,因为所有
Case1
值都是相同的对象

现在,如果
Assert.AreEqual
的参数不是同一个对象,它将继续调用
obj.Equals
。根据您的记录,
Equals
的实现将始终返回false,因为F#编译器没有为其实现相等。为什么?因为某些字段的类型(即,
TestUnion
)不相等。为什么
TestUnion
没有相等性?因为它至少有一个大小写的类型不相等,即,
int->string

如果将
Case1
更改为int的
Case1,然后尝试
Assert.AreEqual(Case1 42,Case1 42)
,测试将失败。这将发生,因为
Case1 42
的两个实例化将不再是同一个对象(除非使用优化编译),并且
TestUnion的
Equals
实现将始终返回false

如果您真的希望这样做(并且您真的知道如何比较函数),您可以自己实现
Equals

[<CustomEquality; NoComparison>]
type TestType = { Foo: TestUnion }
    with 
        override this.Equals other = (* whatever *)
        override this.GetHashCode() = (* whatever *)
这样,如果您缺少某些内容,编译器将始终告诉您:

Assert.IsTrue( a = b )
// The type 'TestType' does not support the 'equality' constraint because blah-blah-blah

F#编译器通常比底层的.NET CLR更正确、更一致。

编辑前面的注释。我玩的时候换了一种。你说得对。当我直接做等式比较时,我得到了错误。很好的解释。我确实尝试了
Assert.AreEqual(case142,case142)
,这确实没有让我感到惊讶。我认为我已经很聪明了,我自己制作了
areEqual
函数,它需要相同的类型(但仍然称为
Assert.areEqual
)。这对于编译器来说仍然过于间接,无法给出错误。
Assert.IsTrue( a = b )
// The type 'TestType' does not support the 'equality' constraint because blah-blah-blah