Module OCaml模块类型和单独编译

Module OCaml模块类型和单独编译,module,ocaml,Module,Ocaml,我正在通读(信中亲切地指着我)。我知道本文讨论了OCaml当前模块类型/签名系统的起源。为此,作者提出了签名中类型声明的不透明解释(允许单独编译)以及清单类型声明(用于表达)。为了将我自己的一些示例放在一起,以演示OCaml模块签名符号试图解决的问题,我在两个文件中编写了以下代码: 在文件ordering.ml(或.mli)中,我两个都试过了(文件A): 在文件useOrdering.ml(文件B)中: 这种想法是希望编译器(在编译第二个文件时)抱怨模块StringOrdering上没有足够的类

我正在通读(信中亲切地指着我)。我知道本文讨论了OCaml当前模块类型/签名系统的起源。为此,作者提出了签名中类型声明的不透明解释(允许单独编译)以及清单类型声明(用于表达)。为了将我自己的一些示例放在一起,以演示OCaml模块签名符号试图解决的问题,我在两个文件中编写了以下代码:

在文件
ordering.ml
(或
.mli
)中,我两个都试过了(文件A):

在文件
useOrdering.ml
(文件B)中:

这种想法是希望编译器(在编译第二个文件时)抱怨模块
StringOrdering
上没有足够的类型信息来检查
StringOrdering.isLess
应用程序(从而激发对
类型语法的需求)。
然而,尽管文件A按预期编译,但文件B导致3.11.2
ocamlc
抱怨语法错误。我理解签名意味着允许某人基于模块签名编写代码,而无需访问实现(模块结构)

我承认,我不确定我在中遇到的语法:
模块A:B
,但这让我想知道是否存在这样或类似的语法(不涉及函子),允许某人仅基于模块类型编写代码,并在链接时提供实际的模块结构,类似于在c/c++中如何使用
*.h
*.c
文件。如果没有这种能力,模块类型/签名似乎基本上是用于密封/隐藏模块内部或更显式的类型检查/注释,而不是用于单独/独立编译


事实上,看看这个例子,我与C编译单元的类比似乎被打破了,因为OCaml手册将OCaml编译单元定义为
A.ml
A.mli
duo,而在C/C++中,
.h
文件被粘贴到任何导入的
.C
文件的编译单元。

正确的方法是执行以下操作:

  • 在ordering.mli中写入:

    (* This define the signature *)
    module type ORDERING = sig
      type t
      val isLess : t -> t -> bool
    end
    
    (* This define a module having ORDERING as signature *)
     module StringOrdering : ORDERING
    
  • 编译文件:
    ocamlc-c ordering.mli

  • 在另一个文件中,请参阅编译后的签名:

    open Ordering
    
    let main () =
      Printf.printf "%b" (StringOrdering.isLess "a" "b")
    
    let () = main ()
    
    编译文件时,会出现预期的类型错误(即
    string
    Ordering.StringOrdering.t
    不兼容)。如果要删除类型错误,应在
    ordering.mli
    中的
    StringOrdering
    定义中添加类型为t=string的
    约束


  • 所以请回答第二个问题:是的,在字节码模式下,编译器只需要知道所依赖的接口,并且可以选择在链接时使用哪个实现。默认情况下,本机代码编译不是这样(因为模块间优化),但可以禁用它

    您可能只是被显式模块和签名定义之间的关系以及通过.ml/.mli文件隐式定义模块之间的关系弄糊涂了

    基本上,如果您有一个文件a.ml并在其他文件中使用它,那么就好像您编写了

    module A =
    struct
      (* content of file a.ml *)
    end
    
    module A :
    sig
      (* content of file a.mli *)
    end =
    struct
      (* content of file a.ml *)
    end
    
    如果您也有a.mli,那么就好像您已经编写了

    module A =
    struct
      (* content of file a.ml *)
    end
    
    module A :
    sig
      (* content of file a.mli *)
    end =
    struct
      (* content of file a.ml *)
    end
    
    请注意,这只定义了名为a的模块,而不是模块类型。不能通过此机制为A的签名命名

    使用的另一个文件可以单独针对.mli进行编译,而根本不提供.ml。但是,您需要确保所有类型信息在需要时都是透明的。例如,假设要定义整数上的映射:

    (* intMap.mli *)
    type key = int
    type 'a map
    val empty : 'a map
    val add : key -> 'a -> 'a map -> 'a map
    val lookup : key -> 'a map -> 'a option
    ...
    
    在这里,
    key
    是透明的,因为任何客户端代码(此签名描述的模块
    IntMap
    的)都需要知道它是什么,才能向映射添加内容。然而,
    map
    类型本身可以(也应该)保持抽象,因为客户端不应该弄乱它的实现细节


    与C头文件的关系是,这些头文件基本上只允许透明类型。在Ocaml中,您可以选择。

    模块字符串排序:排序是一种模块声明。您可以在签名中使用它,即签名包含一个名为
    StringOrdering
    的模块字段,并具有签名
    ORDERING
    。这在模块中没有意义

    您需要在某处定义一个模块来实现所需的操作。模块定义可以类似于

    module StringOrderingImplementation = struct
      type t = string
      let isLess x y = x <= y
    end
    
    然后,
    StringOrderingImplementation.isLess“a”“b”
    类型正确,而
    StringOrderingAbstract.isLess“a”“b”
    无法键入,因为
    StringOrderingAbstract.t
    是一种抽象类型,与
    string
    或任何其他先前存在的类型不兼容。实际上,不可能构建类型为
    StringOrderingAbstract.t
    的值,因为模块不包含任何构造函数

    当您有一个编译单元
    foo.ml
    时,它是一个模块
    foo
    ,该模块的签名由接口文件
    foo.mli
    给出。也就是说,文件
    foo.ml
    foo.mli
    等同于模块定义

    module Foo = (struct (*…contents of foo.ml…*) end :
                  sig (*…contents of foo.mli…*) end)
    
    当编译使用
    Foo
    的模块时,编译器只查看
    Foo.mli
    (或者更确切地说是其编译结果:
    Foo.cmi
    ),而不是查看
    Foo.ml
    )。这就是接口和单独编译如何结合在一起的。C需要
    #include
    ,因为它缺少任何形式的名称空间;在OCaml中,
    Foo.bar
    自动引用编译单元
    Foo
    中定义的
    bar
    ,前提是在sco中没有其他名为
    Foo
    的模块
    module Foo = (struct (*…contents of foo.ml…*) end :
                  sig (*…contents of foo.mli…*) end)