繁体   English   中英

ocaml中“等于(=)”和“相同(==)”之间的区别是什么?

[英]What's the difference between “equal (=)” and “identical (==)” in ocaml?

OCaml ,我们有两种equity comparisons

x = yx == y

那么它们之间的区别究竟是什么呢?

ocam中的x = y就像Java中的x.equals(y)一样吗?

x == y就像Java中的x == y (comparing the address)

x.equals(y)知道x.equals(y)在Java中是如何工作的。 如果进行“深度”比较,则类比非常接近。 需要注意的一点是,物理平等是OCaml(以及一般的功能语言)中的一个滑稽概念。 编译器和运行时系统将移动值,并且可以随意合并和取消合并纯(非可变)值。 所以你应该只使用==如果你真的知道你在做什么。 在某种程度上,它需要熟悉实现(除非必要,否则需要避免)。

OCaml为==做出的具体保证很弱。 可变值的比较方式与您期望的方式相同(即,如果改变其中一个实际上也会突变另一个)。 但是对于非可变值,唯一的保证是物理上相等的值( == )也将比较为等于( = )。 请注意,反之则不然 ,因为sepp2k指出了浮动值。

从本质上讲,语言规范告诉你的非可变值是你可以使用==作为快速检查来判断两个非可变值是否相等( = )。 如果他们比较物理上相等,那么他们在价值方面是相同的。 如果他们不比较物理上的平等,你就不知道他们是否具有相同的价值。 你还是要用=来决定。

编辑:这个答案深入研究了基于Obj模块的OCaml内部工作的细节。 这些知识并不是在没有特别注意的情况下使用的(让我再一次强调这个非常重要的一点:不要将它用于你的程序,但只有当你想要试验 OCaml运行时)。 这些信息也可以获得,尽管可能是在O'Reilly关于OCaml的书中可以理解的形式,可以在线获得 (相当好的书,虽然现在有点过时了)。

=运算符正在检查结构相等性,而==仅检查物理相等性。

等式检查基于值分配和存储在内存中的方式。 OCaml中的运行时值可大致适合2种不同的类别: 盒装或未装箱 前者意味着通过间接在内存中可以访问该值,后者意味着可以直接访问该值。

由于int (32位系统上的int31或64位系统上的int63)是未装箱的值,因此两个运算符的行为都相同。 一些其他类型或值,其运行时实现实际上是int ,也将看到两个运算符与它们的行为相同,如unit () ,空列表[] ,代数数据类型中的常量和多态变体等。

一旦你开始使用涉及结构的更复杂的值,比如列表,数组,元组,记录(C结构等价物),这两个运算符之间的差异就出现了:结构中的值将被加框,除非它们可以被运行时表示为本机int (1)。 这种必要性源于运行时系统必须如何处理值并有效管理内存。 当从其他值构造时分配结构化值,其他值本身可以是结构化值,在这种情况下使用引用(因为它们是盒装的)。

由于分配,在程序的不同点实例化的两个值在物理上不相同的情况下是不太可能的,尽管它们在结构上是相等的。 值中的每个字段或内部元素可以是相同的,甚至达到物理标识,但如果这两个值是动态构建的,那么它们最终会在内存中使用不同的空间,因此在物理上是不同的,但在结构上是相同的。

运行时尝试避免不必要的分配:例如,如果你有一个函数返回总是相同的值(换句话说,如果函数是常量),无论是简单的还是结构化的,该函数将始终返回相同的物理值(即,内存中的相同数据),以便测试物理相等,该函数的两次调用的结果将是成功的。

观察物理运算符何时实际返回true一种方法是在其运行时表示上使用Obj.is_block函数(也就是说, Obj.repr的结果)。 此函数只是告诉它的参数运行时表示是否已装箱。

更人为的方法是使用以下功能:

let phy x : int = Obj.magic (Obj.repr x);;

此函数将返回一个int ,它是指向内存中绑定到x的值的指针的实际值(如果此值被加框) 如果你在int文字上尝试它,你将获得完全相同的值! 那是因为int是未装箱的 (即,该值直接存储在内存中,而不是通过引用)。

现在我们知道盒装值实际上是“引用”值,我们可以推断出这些值可以修改,即使语言表明它们是不可变的。

例如考虑引用类型:

# 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 
  • 如果模块或记录值不包含任何功能值,则它们也是可比较的。

一般来说,我猜可以安全地说,与函数相关的值,或者可能在里面保存函数的值与=不可比,但可以与==进行比较。

显然你应该对这一切非常谨慎 :依赖运行时的实现细节是不正确的 (注意:我在我的答案的初始版本中开玩笑地使用了邪恶这个词,但是由于担心它被认真对待而改变了它)。 正如你在评论中恰当地指出的那样,javascript实现的行为对于浮点数是不同的(在javascript中结构上相同,但在参考实现中没有,以及java的那个?)。


(1)如果我没记错的话,浮点数在存储在数组中时也会被取消装箱以避免双重间接,但是一旦提取它们就会变成盒装,所以你不应该看到盒装值的行为差异。

ocam中的x = y就像Java中的x.equals(y)一样吗?

和x == y就像Java中的x == y(比较地址)?

对,就是那样。 除了在OCaml中你可以在每种值上使用= ,而在Java中你不能在原始类型上使用equals 另一个区别是OCaml中的浮点数是引用类型,所以你不应该使用==比较它们(不是说通常最好直接比较浮点数是否相等)。

总而言之,您基本上应该始终使用=来比较任何类型的值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM