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

发布于 2024-09-09 11:10:28 字数 585 浏览 7 评论 0原文

我试图了解有关 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

未绑定类型构造函数 foobar当然,如果我在 foo.ml 中声明 foobar ,错误也会消失,但这似乎是一种复杂的方法,因为我必须保留每次变化都会同步。

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

提前致谢

I'm trying to understand a specific thing about ocaml modules and their compilation:

am I forced to redeclare types already declared in a .mli inside the specific .ml implementations?

Just to give an example:

(* foo.mli *)
type foobar = Bool of bool | Float of float | Int of int

(* foo.ml *)
type baz = foobar option

This, according to my normal way of thinking about interfaces/implementations, should be ok but it says

Error: Unbound type constructor foobar

while trying to compile with

ocamlc -c foo.mli
ocamlc -c foo.ml

Of course the error disappears if I declare foobar inside foo.ml too but it seems a complex way since I have to keep things synched on every change.

Is there a way to avoid this redundancy or I'm forced to redeclare types every time?

Thanks in advance

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

坦然微笑 2024-09-16 11:10:28

OCaml 尝试强制您将接口 (.mli) 与实现 (.ml) 分开。大多数时候,这是一件好事;对于值,您可以发布您可以说 OCaml 强制执行一定程度的抽象(必须发布接口;接口中没有代码),

通常情况下,实现与实现相同 。接口:两者都声明类型具有特定的表示形式(并且类型声明可能是生成的)。这里不能有抽象,因为实现者没有任何他不想要的关于类型的信息。 (例外情况基本上是在声明抽象类型时。)

一种看待它的方法是,给定接口 type foobar = Bool of bool | Float of float 。 | Int of int,只有一种可能的实现,所以不要编写实现!

一个常见的习惯用法是有一个专门用于类型声明的模块,并使其只有一个 .mli。由于类型不依赖于值,因此该模块通常出现在依赖链的早期。大多数编译工具都能很好地应对这一点;例如 ocamldep 会做正确的事情。 (与只有 .ml 相比,这是一个优点。)

这种方法的局限性在于您还需要到处都有一些模块定义。 (一个典型的示例是定义类型 foo,然后定义一个带有 type t = fooOrderedFoo : Map.OrderedType 模块,然后再定义一个类型声明涉及'a Map.Make(OrderedFoo).t。)这些不能放入接口文件中。有时,将定义分解为几个块是可以接受的,首先是一堆类型(types1.mli),然后是一个模块(mod1.mlimod1.mli)。 ml),然后是更多类型(types2.mli)。其他时候(例如,如果定义是递归的),您必须忍受没有 .mli.ml 或重复。

OCaml tries to force you to separate the interface (.mli) from the implementation (.ml. Most of the time, this is a good thing; for values, you publish the type in the interface, and keep the code in the implementation. You could say that OCaml is enforcing a certain amount of abstraction (interfaces must be published; no code in interfaces).

For types, very often, the implementation is the same as the interface: both state that the type has a particular representation (and perhaps that the type declaration is generative). Here, there can be no abstraction, because the implementer doesn't have any information about the type that he doesn't want to publish. (The exception is basically when you declare an abstract type.)

One way to look at it is that the interface already contains enough information to write the implementation. Given the interface type foobar = Bool of bool | Float of float | Int of int, there is only one possible implementation. So don't write an implementation!

A common idiom is to have a module that is dedicated to type declarations, and make it have only a .mli. Since types don't depend on values, this module typically comes in very early in the dependency chain. Most compilation tools cope well with this; for example ocamldep will do the right thing. (This is one advantage over having only a .ml.)

The limitation of this approach is when you also need a few module definitions here and there. (A typical example is defining a type foo, then an OrderedFoo : Map.OrderedType module with type t = foo, then a further type declaration involving'a Map.Make(OrderedFoo).t.) These can't be put in interface files. Sometimes it's acceptable to break down your definitions into several chunks, first a bunch of types (types1.mli), then a module (mod1.mli and mod1.ml), then more types (types2.mli). Other times (for example if the definitions are recursive) you have to live with either a .ml without a .mli or duplication.

温馨耳语 2024-09-16 11:10:28

是的,您被迫重新声明类型。据我所知,解决这个问题的唯一方法是

  • 不要使用 .mli 文件;只是暴露一切,没有界面。糟糕的主意。

  • 使用文学编程工具或其他预处理器来避免在 One True Source 中重复接口声明。对于大型项目,我们在我的小组中执行此操作。

对于小型项目,我们只需重复类型声明。并为此发牢骚。

Yes, you are forced to redeclare types. The only ways around it that I know of are

  • Don't use a .mli file; just expose everything with no interface. Terrible idea.

  • Use a literate-programming tool or other preprocessor to avoid duplicating the interface declarations in the One True Source. For large projects, we do this in my group.

For small projects, we just duplicate type declarations. And grumble about it.

香草可樂 2024-09-16 11:10:28

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

ocamlc -i some.ml > some.mli

You can let ocamlc generate the mli file for you from the ml file:

ocamlc -i some.ml > some.mli
舂唻埖巳落 2024-09-16 11:10:28

一般来说,是的,您需要复制类型。

不过,您可以使用 Camlp4 和 pa_macro 语法扩展(findlib 包:camlp4.macro)解决此问题。除其他事项外,它还定义了 INCLUDE 构造。您可以使用它将常见类型定义分解为单独的文件,并将该文件包含在 .ml.mli 文件中。然而,我还没有在已部署的 OCaml 项目中看到这样做,所以我不知道它是否符合推荐的做法,但这是可能的。

然而,在我看来,文字编程解决方案更干净。

In general, yes, you are required to duplicate the types.

You can work around this, however, with Camlp4 and the pa_macro syntax extension (findlib package: camlp4.macro). It defines, among other things, and INCLUDE construct. You can use it to factor the common type definitions out into a separate file and include that file in both the .ml and .mli files. I haven't seen this done in a deployed OCaml project, however, so I don't know that it would qualify as recommended practice, but it is possible.

The literate programming solution, however, is cleaner IMO.

临风闻羌笛 2024-09-16 11:10:28

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

No, in the mli file, just say "type foobar". This will work.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文