Browse thread
automatic construction of mli files
[
Home
]
[ Index:
by date
|
by threads
]
[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
| Date: | -- (:) |
| From: | John Prevost <jmp@a...> |
| Subject: | Re: automatic construction of mli files |
>>>>> "ja" == Julian Assange <proff@iq.org> writes:
ja> .mli files are highly redundant. Almost without exception all,
ja> or at the vast majority of .mli information can be generated
ja> from the underlying .ml implementation. We have programming
ja> languages to reduce redundancy, not increase it. Keeping mli
ja> and ml files in-sync is not only a waste of time, but
ja> error-prone and from my survey often not performed correctly,
ja> particularly where consistency is not enforced by the compiler
ja> (e.g comments describing functions and types). While exactly
ja> the same problem exists in a number of other
ja> separate-compilation language implementations, we, as camlers,
ja> should strive for something better.
I disagree. .mli files are not at all redundant. For one thing, if
you do not desire to use .mli files, you do not need to write them at
all--the system works perfectly well if you use only .ml files.
What .mli files are *for* is to restrict the type of a top-level
module. Within the language we may write:
module Foo =
(struct
type t = int
let foo x = x
let bar x = x + 1
let baz x = x
end : sig
type t
val foo : int -> t
val bar : t -> t
val baz : t -> int
end)
in order to restrict the visibility of types. But using top-level
modules, there's no declaration which may be wrapped in a type
constraint. .mli serves exactly this purpose.
Now--what if the language were changed to annotate such things
in-line? I argue that it would not, in fact, become a better
language. For the above, we might write:
EXTERN opaque
type t = int
EXTERN int -> t
let foo x = x
EXTERN t -> t
let bar x = x + 1
EXTERN t -> int
let baz x = x
Note that I don't actually recommend this syntax, I'm just trying to
point out what information has to be provided.
The first thing to note is that each top-level declaration must be
annotated with the type you wish to export, if you want to export
anything at all. I'm of two minds about this. On the one level,
there are good arguments for doing it that way: the type constraints
are near the code they go with. When I look at foo, I see that it's
actually meant to turn an int into a t.
On the other hand, it really breaks things up. When I see:
type t
val foo : int -> t
val bar : t -> t
val baz : t -> int
it's very easy to see what basic types and operations on those types
are provided by the module. If I want to change what information is
revealed, it's pretty easy to do.
And things get even hairier when you want to restrict things down
more from more complex types.
I personally think that making type constraints an aspect of the
module-level language, and hence not supporting inline declarations of
this sort of thing is good. If you want things to be transparent,
don't use .mli files. In general, if you want to hide anything, I
think you usually want to hide enough to make inline constraints more
confusing than not.
John.