我正在尝试理解有关ocaml模块及其编译的具体事项:
我是否被迫重新声明已在特定.mli
实施中的.ml
中声明的类型?
举一个例子:
(* foo.mli *)
type foobar = Bool of bool | Float of float | Int of int
(* foo.ml *)
type baz = foobar option
根据我对接口/实现的正常思考方式,这应该没问题,但是它说
尝试使用进行编译时错误:未绑定类型构造函数foobar
ocamlc -c foo.mli
ocamlc -c foo.ml
当然,如果我在foobar
内声明foo.ml
,错误就会消失,但这似乎很复杂,因为我必须在每次更改时保持同步。
有没有办法避免这种冗余,或者我每次都被迫重新声明类型?
提前致谢
答案 0 :(得分:18)
是的,您被迫重新声明类型。我所知道的唯一方法是
不要使用.mli文件;只是暴露一切没有界面。可怕的想法。
使用文字编程工具或其他预处理器来避免重复One True Source中的接口声明。对于大型项目,我们在我的小组中执行此操作。
对于小型项目,我们只是复制类型声明。抱怨它。
答案 1 :(得分:17)
OCaml试图强制你将接口(.mli
)与实现(.ml
分开。大多数时候,这是一件好事;对于值,你在界面中发布类型并且将代码保留在实现中。您可以说OCaml正在执行一定量的抽象(必须发布接口;接口中没有代码)。
对于类型,通常,实现与接口相同:都声明类型具有特定表示(并且可能类型声明是生成的)。这里,没有抽象,因为实现者没有关于他不想发布的类型的任何信息。 (例外情况基本上是在声明抽象类型时。)
查看它的一种方法是接口已经包含足够的信息来编写实现。给定接口type foobar = Bool of bool | Float of float | Int of int
,只有一种可能的实现。所以不要写一个实现!
一个常见的习惯用法是拥有一个专用于声明类型的模块,并使其只有.mli
。由于类型不依赖于值,因此该模块通常在依赖链中很早就出现。大多数编译工具都能很好地应对这种情况;例如ocamldep
将做正确的事情。 (与仅.ml
相比,这是一个优势。)
这种方法的局限性在于您还需要一些模块定义。 (一个典型的例子是定义类型foo
,然后是OrderedFoo : Map.OrderedType
模块type t = foo
,然后是另一个涉及'a Map.Make(OrderedFoo).t
的类型声明。)这些不能放在接口中文件。有时可以将您的定义分解为几个块,首先是一堆类型(types1.mli
),然后是一个模块(mod1.mli
和mod1.ml
),然后是更多类型({{1} })。其他时候(例如,如果定义是递归的),您必须使用types2.mli
而不是.ml
或重复。
答案 2 :(得分:13)
您可以让ocamlc从ml文件中为您生成mli文件:
ocamlc -i some.ml > some.mli
答案 3 :(得分:3)
一般情况下,是的,您需要复制类型。
但是,您可以使用Camlp4和pa_macro
语法扩展(findlib包:camlp4.macro
)解决此问题。除其他外,它定义了INCLUDE和INCLUDE构造。您可以使用它将公共类型定义分解为单独的文件,并将该文件包含在.ml
和.mli
文件中。我没有在已部署的OCaml项目中看到这样做,所以我不知道它是否符合推荐的做法,但它是可能的。
然而,有文化的编程解决方案是更清洁的IMO。
答案 4 :(得分:-3)
不,在mli文件中,只说“type foobar”。这将有效。