在OCaml
中,我们有两种equity comparisons
:
x = y
和x == y
,
那么它们之间的区别究竟是什么呢?
ocaml中的x = y
就像Java中的x.equals(y)
一样吗?
和x == y
就像Java中的x == y (comparing the address)
一样?
答案 0 :(得分:28)
我不确切知道x.equals(y)
在Java中是如何工作的。如果进行“深度”比较,则类比非常接近。需要注意的一点是,物理平等是OCaml(以及一般的功能语言)中的一个滑稽概念。编译器和运行时系统将移动值,并且可以随意合并和取消合并纯(非可变)值。所以如果你真的知道自己在做什么,那么你应该只使用==
。在某种程度上,它需要熟悉实现(除非必要,这是必须避免的)。
OCaml对==
的具体保证很弱。可变值在物理上等于您期望的方式进行比较(即,如果突变两者中的一个实际上也会使另一个突变)。但对于非可变值,唯一的保证是物理上相等的值(==
)也将比较为相等(=
)。请注意,反之为不为真,因为sepp2k指出浮动值。
从本质上讲,语言规范告诉您的非可变值是您可以使用==
作为快速检查来确定两个不可变值是否相等(=
)。如果他们比较物理上相等,那么他们在价值方面是相同的。如果他们不比较物理上的平等,你就不知道他们是否具有相同的价值。您仍然必须使用=
来决定。
答案 1 :(得分:17)
编辑:这个答案深入研究了基于Obj
模块的OCaml内部工作的细节。这些知识并不意味着在没有额外关注的情况下使用(让我再次强调这个非常重要的一点:不要将它用于你的程序,但只有你希望使用OCaml运行时实验。这些信息也是可以获得的,尽管可能是在OCaml的O< Reilly书中更容易理解的形式,可用online(相当好的书,虽然现在有点过时了。)
=
运算符正在检查结构相等性,而==
仅检查物理相等性。
平等检查基于值分配和存储在内存中的方式。 OCaml中的运行时值可大致分为两类:盒装或未装箱。前者意味着通过间接在内存中可以访问该值,后者意味着可以直接访问该值。
由于int
(32位系统上的int31或64位系统上的int63)是未装箱的值,因此两个运算符的行为都相同。一些其他类型或值,其运行时实现实际上是int
,也将看到两个运算符与它们的行为相同,例如单元()
,空列表[]
,代数数据类型中的常量和多态变体等。
一旦你开始使用涉及结构的更复杂的值,比如列表,数组,元组,记录(C结构等价物),这两个运算符之间的差异就出现了:结构中的值将被加框,除非它们可以是运行时表示的作为本土的英特尔(1)。这种必要性源于运行时系统必须如何处理值并有效管理内存。结构化值在从其他值构造时分配,其他值本身可以是结构化值,在这种情况下使用引用(因为它们是盒装的)。
由于分配,在程序的不同点实例化的两个值在物理上不相同的情况下是非常不可能的,尽管它们在结构上是相等的。值中的每个字段或内部元素可以是相同的,甚至达到物理标识,但如果这两个值是动态构建的,那么它们最终会在内存中使用不同的空间,因此在物理上是不同的,但在结构上是相同的
运行时尝试避免不必要的分配:例如,如果你有一个函数返回总是相同的值(换句话说,如果函数是常量),无论是简单的还是结构化的,那个函数将总是返回相同的物理value(即内存中的相同数据),以便测试物理相等性,两次调用该函数的结果将成功。
观察物理运算符何时实际返回true
的一种方法是在其运行时表示上使用Obj.is_block
函数(也就是说,Obj.repr
的结果) 。此函数只是告诉它的参数运行时表示是否已装箱。
更人为的方法是使用以下功能:
let phy x : int = Obj.magic (Obj.repr x);;
此函数将返回int
,这是指向内存中绑定到x
的值的指针的实际值,如果此值为框。如果您在int
文字上试用它,您将得到完全相同的值!这是因为int unboxed (即,该值直接存储在内存中,而不是通过引用)。
现在我们知道盒装值实际上是"引用"值,我们可以推断出这些值可以修改,即使语言表明它们是不可变的。
例如考虑引用类型:
# type 'a ref = {mutable contents : 'a };;
我们可以像这样定义一个不可变的引用:
# type 'a imm = {i : 'a };;
type 'a imm = {i : 'a; }
然后使用Obj.magic
函数将一种类型强制转换为另一种类型,因为从结构上讲,这些类型将被简化为相同的运行时表示。
例如:
# let x = { i = 1 };;
- : val x : int imm = { i = 1 }
# let y : int ref = Obj.magic x;;
- : val y : int ref = { contents = 1 }
# y := 2;;
- : unit = ()
# x
- : int imm = { i = 2 }
有一些例外:
如果值是对象,那么即使看似结构相同的值也会在结构比较中返回false
# let o1 = object end;;
val o1 : < > = <obj>
# let o2 = object end;;
val o2 : < > = <obj>
# o1 = o2;;
- : bool = false
# o1 = o1;;
- : bool = true
这里我们看到=
恢复到物理等效。
如果值是函数,则无法在结构上进行比较,但物理比较可以按预期工作。
懒惰值可能会也可能不会在结构上具有可比性,这取决于它们是否被强制(分别)。
# let l1 = lazy (40 + 2);;
val l1 : lazy_t = <lazy>
# let l2 = lazy (40 + 2);;
val l2 : lazy_t = <lazy>
# l1 = l2;;
Exception: Invalid_argument "equal: functional value".
# Lazy.force l1;;
- : int = 42
# Lazy.force l2;;
- : int = 42
# l1 = l2;;
- : bool = true
如果模块或记录值不包含任何功能值,则它们也具有可比性。
一般情况下,我猜可以安全地说,与函数相关的值或者可能包含函数的值与=
不可比,但可以与==
进行比较。
显然要非常谨慎:依赖于运行时的实现细节不正确(注意:我开玩笑地使用了 evil < / em>在我的答案的初始版本中,但由于害怕被认真对待而改变了它。正如你在评论中恰当地指出的那样,javascript实现的行为对于浮点数是不同的(在javascript中结构上相同,但在参考实现中没有,那么java的实现呢?)。
<小时/> (1)如果我没记错的话,当存储在数组中时浮动也会被取消装箱以避免双重间接,但是一旦被提取它们就会变成盒子,所以你不应该看到盒装行为的差异值。
答案 2 :(得分:8)
ocaml中的x = y就像Java中的x.equals(y)一样吗?
和x == y就像Java中的x == y(比较地址)?
是的,就是这样。除了在OCaml中,您可以在每种值上使用=
,而在Java中,您不能在原始类型上使用equals
。另一个区别是OCaml中的浮点数是引用类型,所以你不应该使用==
来比较它们(不是说通常最好直接比较浮点数是否相等)。
总而言之,您基本上应该始终使用=
来比较任何类型的值。
答案 3 :(得分:2)