OCaml

时间:2018-02-20 23:12:42

标签: ocaml

我无法理解OCaml中模块的相等性。编程器应该是适用的(这是互联网所声称的),但有时似乎失败了,我无法完全看到它背后的一般规则。

这是我的示例代码:

module type PT = sig end
module P = struct end

let () =
  Random.self_init ()

module OrigHashtbl = Hashtbl
module Hashtbl = struct
  module Make(Hash: OrigHashtbl.HashedType) = struct
    let random = Random.int 1000000

    type 'a t = { t_list: (Hash.t * 'a) list }

    let create _ =
      Format.printf "Random %d@." random;
      { t_list = [] }          

    let mem ht v =
      Format.printf "Random %d@." random;
      List.mem_assoc v ht.t_list          
  end
end

module Hash = struct
  type t = int
  let equal x1 x2 = x1 = x2
  let hash x = x
end

module Wrap(P: PT) = struct
  module H = Hashtbl.Make(Hash)
end

module Wrap1 = Wrap(P)
module Wrap2 = Wrap(P)
module H1 = Wrap1.H
module H2 = Wrap2.H

let () =
  let ht = H1.create 16 in
  Format.printf "%b@." (H2.mem ht 0)

ideone上的代码:https://ideone.com/5C8Muk

我在这里做的是从Hashtbl模块创建一些函数的虚拟实现,并将其包装在我调用的函数Wrap中。两次,创建H1H2可以互换使用,尽管它们是不同的模块,可以捕获random的不同值:

$ ./test.byte 
Random 501586
Random 681009
false

这是预期的,因为正如互联网所声称的那样,OCaml仿函数是适用的。

然后我尝试将Hash模块移到Wrap内,程序停止编译。

module Wrap(P: PT) = struct
  module Hash = struct
    type t = int
    let equal x1 x2 = x1 = x2
    let hash x = x
  end

  module H = Hashtbl.Make(Hash)
end

Ideone的代码:https://ideone.com/Gjxc32

$ ocamlbuild test.byte
+ /home/XXX/.opam/4.04.0/bin/ocamlc.opt -c -o test.cmo test.ml
File "test.ml", line 41, characters 35-37:
Error: This expression has type 'a H1.t = 'a Hashtbl.Make(Wrap1.Hash).t
       but an expression was expected of type
         'b H2.t = 'b Hashtbl.Make(Wrap2.Hash).t

为什么?我希望如果Wrap1Wrap2是相同的模块(因为仿函数应该是适用的,对吗?),那么Wrap1.HashWrap2.Hash也是相同的。比较模块背后的一般规则是什么?

注意:这是另一个问题How to convince ocaml that two functor instantiations are equal的延续。我得到的唯一答案是" OCaml仿函数是生成性的"这是假的(至少有时候)。

修改

也许,我错误地询问模块的平等性。我真正感兴趣的是类型相等。为什么在某些情况下Wrap1.H.t等于Wrap2.H.t,在某些情况下 - 不等。

答案

与@Drup讨论后,对我来说事情变得更加清晰。 Applicativeness表示以下内容:if A = BF(A) =/= F(B)F(A).t = F(B).tF(A)。对于F(B)Wrap1.H.t = Wrap2.H.t中定义的模块,事情取决于如何定义这些模块。在我的示例中,H是否取决于Wrap1.H.t = Hashtbl(Hash).t = Wrap2.H.t的定义。在编译的变体中,Wrap1.H.t = Hashtbl(Wrap1.Hash).t。在不编译的变体Wrap2.H.t = Hashtbl(Wrap2.Hash).t<span id="submit-btn" class="btn">Submit</span> 中,它们是不同的。

1 个答案:

答案 0 :(得分:5)

“应用仿函数”表示A = B暗示F(A).t = F(B).t。它意味着F(A).M = F(B).M。这是关于类型,而不是模块。

创建类型和创建模块之间的一个基本区别是创建类型是无副作用的(因此可以使用适用行为来处理)。创建模块不是副作用,因此您不能将两个不同的新模块视为相同。 octachron在最后一个答案中给出了一个很好的例子。

如果要在仍具有本地模块的同时保持相等性,可以使用模块别名。如果你这样做:

module Hash0 = struct
  type t = int
  let equal x1 x2 = x1 = x2
  let hash x = x
end
module Wrap(P: PT) = struct
  module Hash = Hash0 (* the type of this module is "= Hash0" *)
  module H = Hashtbl.Make(Hash)
end

然后程序被接受。

请注意,即使在您的失败版本中,Wrap1.Hash.t = Wrap2.Hash.t仍然存在(无论其定义如何),即使模块不相等。