30

OCaml中,我们有两种equity comparisons

x = yx == y,

那么它们之间的确切区别是什么?

在 ocaml 中是不是x = y就像x.equals(y)在 Java 中一样?

x == y就像x == y (comparing the address)在Java中一样?

4

4 回答 4

33

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

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

本质上,对于非可变值,语言规范告诉您的是,您可以将==其用作快速检查来确定两个非可变值是否相等(=)。如果它们在物理上比较相等,那么它们在价值方面是相等的。如果它们在物理上不相等,您将不知道它们在价值方面是否相等。你还是得=用来决定。

于 2012-11-27T18:11:39.590 回答
21

Obj编辑:这个答案基于模块深入探讨了 OCaml 内部工作的细节。这些知识不应该在没有额外注意的情况下使用(让我再次强调这个非常重要的一点:不要将它用于您的程序,但前提是您希望尝试使用 OCaml 运行时)。该信息也可用,尽管在 O'Reilly 关于 OCaml 的书中可能以更易于理解的形式,可在线获取(相当不错的书,虽然现在有点过时了)。

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

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

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

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

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

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

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

一种更人为的方法是使用以下函数:

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

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

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

例如考虑引用类型:

# type 'a ref = {mutable contents : 'a };;

我们可以像这样定义一个不可变的 ref:

# 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) 如果我没记错的话,浮点数在存储在数组中以避免双重间接时也会被取消装箱,但是一旦提取它们就会被装箱,所以你不应该看到装箱值的行为差异。

于 2012-11-28T01:20:54.877 回答
9

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

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

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

所以总而言之,你基本上应该总是=用来比较任何类型的值。

于 2012-11-27T18:06:41.290 回答
2

根据http://rigaux.org/language-study/syntax-across-languages-per-language/OCaml.html==检查浅层相等,并=检查深层相等

于 2012-11-27T18:07:13.340 回答