OCaml类型声明中的冗余性 (ml/mli)

42

我正在尝试理解关于OCaml模块及其编译的一个具体问题:

在特定的.ml实现中,我是否被迫重新声明已在.mli中声明的类型?

举个例子:

(* 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

当然,如果我也在foo.ml内部声明foobar,那么错误就消失了,但这似乎是一种复杂的方法,因为我必须在每次更改时保持同步。

有没有办法避免这种冗余,或者我必须每次重新声明类型?

提前感谢

5个回答

20

是的,你被迫重新声明类型。我知道的唯一解决办法是:

  • 不使用 .mli 文件;只公开所有内容而没有接口。这是一个可怕的想法。

  • 使用文学编程工具或其他预处理器,以避免在 One True Source 中重复接口声明。对于大型项目,我们在我的团队中采用了这种方法。

对于小型项目,我们只是重复类型声明。并抱怨它。


我认为不应该限制允许.ml文件推断与耦合的.mli中描述的类型。据我所知,实际实现__强制冗余__,但也要求__两个签名必须相等__,因此这实际上是将相同的声明加倍。这就是为什么我认为它应该意识到这些声明,而不必复制和粘贴它们。根据我的理解,类型推断算法不会出现问题。 - Jack
4
它并不完全强制冗余,也不要求签名相同,只是在 ml 文件中的声明必须等于或更具体于 mli 声明。mli 的目的是定义接口中可见的内容,因此您可以选择不公开类型(在这种情况下它不在 mli 文件中),或者您可以选择公开有一种类型,但不公开如何使用它(在这种情况下类型声明是不同的)。当然,在您的情况下,编译器假定类型是完整定义在 mli 中的,这是有意义的。 - Niki Yoshiuchi
3
@Niki,在 value 声明中没有强制的冗余(在 .ml 中这些声明本身就是可选的),实际上这也是“至少与”所涉及的地方。但在99%的情况下,在接口中声明的 显式类型 与实现中类型的定义是相同的。这是多余且令人烦恼的,但作为一名语言设计者,我已经认真思考了这个问题,而且我还没有提出一个既有原则性又比OCaml显著更好的建议。 - Norman Ramsey
1
实际上相反 - 在类型中指定不同的结构和签名是非常常见的。请参阅http://caml.inria.fr/pub/docs/manual-ocaml/manual016.html#toc54以获取摘要。 - ygrek
我确实见过许多抽象类型,虽然在mlis中完全定义的类型很常见,但遗憾的是OCaml没有提供防止冗余的方法(根据更专业的Norman所说,这占了绝大部分用途)。 - Niki Yoshiuchi
显示剩余3条评论

20
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.mlimod1.ml),然后是更多类型(types2.mli)。其他时候(例如如果定义是递归的),您必须接受没有.mli.ml或重复的情况。

17

你可以让ocamlc从ml文件中为你生成mli文件:

ocamlc -i some.ml > some.mli

3
一般来说,是的,您需要复制这些类型。但是,您可以使用Camlp4和pa_macro语法扩展(findlib包:camlp4.macro)来解决此问题。它定义了INCLUDE结构等内容。您可以使用它将常见类型定义分解到单独的文件中,并将该文件包含在.ml和.mli文件中。然而,我并没有看到在部署的OCaml项目中采用此方法,因此我不确定它是否符合推荐做法,但这是可能的。相比之下,文学编程方案更加干净。

-3

不,在mli文件中,只需写“type foobar”。这将起作用。


不,复制类型并仅声明它会有不同的效果。如果你只是说“type foobar”,使用你的模块的人将能够拥有类型为foobar的变量,但无法访问其内部。例如,他们将无法在没有帮助函数的情况下实例化它们,也无法对它们的值进行模式匹配。 - Nephanth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接