views:

131

answers:

4

Hello, 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

+3  A: 

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.

Norman Ramsey
Jack
It doesn't quite force redundancy nor does it require the signatures be identical, just that the declaration in the ml file must be equal or more specific than the mli declaration. The point of the mli is to define what's visible in the interface, as such you can choose not to expose the type (in which case it's not in the mli file) or you can choose to expose that there is a type, but not how it's used (in which case the type declarations are different). Of course in your situation it would make sense for the compiler to just assume the type since it is full defined in the mli.
Niki Yoshiuchi
@Niki there's no forced redundancy in *value* declarations (which are optional in the .ml anyway), and in practice that's where "at least as specific as" comes in. But in 99% of cases a *manifest type* declared in an interface is identical to the definition of the type in the implementation. This is redundant, and irritating, but as a language designer I have thought hard about this problem and I haven't come up with a proposal that I think is both principled and significantly better than OCaml.
Norman Ramsey
@Niki I can understand that you may want to force a narrower type as an argument to a value definition but this doesn't happen with types, also because usually they are the bridge that allows you to use functions from outside the module with their custom types.. so as Norman said 99% of the time you just have the same types declared in mli and in ml.. by the way it seems that OCaml is not so widely used, always same people answers to my questions.. it's a shame since it is a really elegant language, that's why I hoped that a less muddled way to achieve what I wanted existed.
Jack
Actually the contrary - it is quite common with types to specify different representation in structure and it's signature. See http://caml.inria.fr/pub/docs/manual-ocaml/manual016.html#toc54 for the summary.
ygrek
I've certainly seen a number of abstracted types although fully defined types in mlis are common enough that it's a real shame OCaml doesn't provide some method of preventing redundancy (and according to Norman who is much more of an expert on the matter, this makes up the vast majority of uses).
Niki Yoshiuchi
@ygrek I'm surprised to hear you say so. In my experience it is quite common for a type to be *abstract* in a signature and manifest in a structure. It is very rare for a type to be manifest in a signature and to have a *different* definition in a structure. In fact, I would quite enjoy seeing a (non-contrived) example.(More-polymorphic-than-expected, I bet.)
Norman Ramsey
@Norman: sorry, apparently I used wrong wording - meant abstracted types in signatures
ygrek
+1  A: 

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

ocamlc -i some.ml > some.mli
aneccodeal
A: 

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.

Michael E
+1  A: 

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.

Gilles