Version française
Home     About     Download     Resources     Contact us    
Browse thread
automatic construction of mli files
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ 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.