在 F# 中如何检查引用相等性?

32

F#使用结构相等性来实现=操作符,这几乎总是你想要的:

let a = [1; 2; 3]
let b = [1; 2; 3]
printfn "%A" (a = b)  // Prints "true"

但在一些算法中,能够询问“这两个东西是同一个对象吗?”可能非常重要。例如,这可以帮助检测图中的循环。那么我如何在F#中询问引用相等性呢?也就是说,我如何编写下面的isSameObject函数?

let isSameObject x y = ???
let a = [1; 2; 3]
let b = [1; 2; 3]
let a' = a
printfn "%A" (isSameObject a b)  // Prints "false"
printfn "%A" (isSameObject a a')  // Prints "true"
1个回答

54
答案是使用LanguagePrimitives.PhysicalEquality函数:
let isSameObject = LanguagePrimitives.PhysicalEquality
let a = [1; 2; 3]
let b = [1; 2; 3]
let a' = a
printfn "%A" (isSameObject a b)  // Prints "false"
printfn "%A" (isSameObject a a')  // Prints "true"

我在Stack Overflow上只找到了一个与此相关的问题:如何在F#中使用短路等号检查?。由于那个问题的主题几乎让我忽略了它,所以我认为我会再问一遍这个问题(并回答)。希望这个问题的主题行会使它更容易被搜索到,比如“在F#中的引用相等性”等词汇。

obj.ReferenceEquals怎么样?

Fyodor Soikin在评论中问obj.ReferenceEquals有什么问题。答案是“没有太多问题”,但是对于大多数F#代码,LanguagePrimitives.PhysicalEqualityobj.ReferenceEquals更好的两个方面是:

1)当您传递给它两种不同的类型时,PhysicalEquality会抛出编译器错误,而obj.ReferenceEquals只接受两个obj,因此会尝试将int listchar list进行比较。

let a = [1;2;3]
let b = ['a';'b';'c']

obj.ReferenceEquals(a,b) // Returns false
LanguagePrimitives.PhysicalEquality a b // Compiler error

2) PhysicalEquality 不允许比较值类型,只能比较引用类型。使用 obj.ReferenceEquals 可以比较两个值类型,并且会隐式地装箱它们。但是它会分别对每个值类型进行装箱,这意味着即使你给了它“相同”的值对象,它也会始终返回 false:

let n = 3
let n' = n
obj.ReferenceEquals(n,n') // Returns false!
LanguagePrimitives.PhysicalEquality n n' // Compiler error

当然,还有一个区别,归结为个人喜好和易用性。 PhysicalEquality 接受柯里化样式的参数,这与类型推断和部分应用程序很好地配合。 obj.ReferenceEquals 接受元组样式的参数,这意味着它稍微难看一些。
由于所有这些原因,在几乎每种情况下,使用 LanguagePrimitives.PhysicalEquality 要比使用 obj.ReferenceEquals 更好。

3
@FyodorSoikin - 我曾对你的问题写过两条评论,后来意识到放在答案中会更好,并附有代码示例。因此,obj.ReferenceEquals 并不是 不好 的选择,但 PhysicalEquality 更好,因为如果你尝试比较两种不同类型的对象,它将会返回类型错误(这几乎肯定是你代码中的一个bug,而你希望编译器能够帮助你捕捉到它)。 - rmunn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接