[MLton-user] ascription in MLton source
Matthew Fluet
fluet at tti-c.org
Sat Feb 23 01:33:28 PST 2008
On Sat, 23 Feb 2008, Sean McLaughlin wrote:
> I've been browsing the MLton source, and am just curious about something:
> why is there almost no opaque ascription? It seems the only time it's used
> is in substructures where you define the signature inline.
> I learned to always prefer opaque ascription
> except in certain circumstances where having to declare lots of type
> equalities
> makes it unwieldy. Is there a totally different philosophy
> in the MLton architecture? Is there
> something particular about the compiler that required, or was made
> simpler/cleaner
> using transparent ascription?
Stephen Weeks wrote nearly all of the initial compiler code, and established
various programming policies. He might have some additional thoughts, but I
think the short answer is that various other aspects of the programming
style used in MLton obviate the need for opaque ascription.
First, we make extensive use of closed signatures and functors. (See
<src>/doc/style-guide/.) Nearly every module is implemented as a functor
of the form:
signature CLOSURE_CONVERT_STRUCTS =
sig
structure Sxml: SXML
structure Ssa: SSA
sharing Sxml.Atoms = Ssa.Atoms
end
signature CLOSURE_CONVERT =
sig
include CLOSURE_CONVERT_STRUCTS
val closureConvert: Sxml.Program.t -> Ssa.Program.t
end
functor ClosureConvert(S: CLOSURE_CONVERT_STRUCTS): CLOSURE_CONVERT =
struct
...
end
So, the closure conversion module/function can't exploit any data
implementation details of the Sxml or Ssa modules other than those
revealed by the SXML and SSA signatures. Furthermore, ClosureConvert
doesn't define/export any new types, so there is no need for an opaque
ascription on the functor result. In fact, if we were to do so, we would
actually need a number of sharing constraints to relate the Sxml and Ssa
structures in the result CLOSURE_CONVERT to the Sxml and Ssa structures in
the source CLOSURE_CONVERT_STRUCTS.
Even for functors that define types (e.g.,
<src>/mlton/ssa/ssa-tree.{sig,fun}), there is no need for an opaque
ascription, as all the clients of the types are themselves functors that
are parameterized by 'structure Ssa: SSA' -- so any implementation details
that would be revealed by the transparent signature ascription can't be
exploited by the clients. It is only at the top level
(<src>/mlton/main/compile.fun) all the IL functors are instantiated and
all of the compiler pass functors are instantiated. While it would be
possible to exploit leaked implmenentation details within compile.fun, it
is effectively just the linker, and just dispatches to functions from
other (now instantited) modules.
Second, nearly every declared type is defined as a datatype, which is
generative and creates a new type. If the datatype constructor(s) are not
revealed by the signature, then the type is effectively opaque.
Consider:
structure S1 : sig type t end = struct datatype t = T of int end
structure S2 :> sig type t end = struct type t = int end
Although S1 uses a transparent signature ascription, no client of S1 can
exploit the data implementation details -- there is no way to create or
destruct values of type S1.t without the S1.T data constructor.
Taken together, these programming conventions ensure the same data
abstractions that are ensured by opaque ascription.
Indeed, the latter convention (using a datatype without exporting
constructors) would seem to cover the few places where we do use an opaque
ascription on a sub-structure with an inline signature. And it also
covers the standard motivating examples of opaque ascription:
signature QUEUE =
sig
type 'a queue
exception Empty
val empty : 'a queue
val insert : 'a * 'a queue -> 'a queue
val remove : 'a queue -> 'a * 'a queue
end
structure Queue2 :> QUEUE =
struct
type 'a queue = 'a list * 'a list
val empty = (nil, nil)
fun insert (x, (bs, fs)) = (x::bs, fs)
exception Empty
fun remove (nil, nil) = raise Empty
| remove (bs, f::fs) = (f, (bs, fs))
| remove (bs, nil) = remove (nil, rev bs)
end
structure Queue1 : QUEUE =
struct
datatype 'a queue = T of 'a list * 'a list
val empty = T (nil, nil)
fun insert (x, T (bs, fs)) = T (x::bs, fs)
exception Empty
fun remove (T (nil, nil)) = raise Empty
| remove (T (bs, f::fs)) = (f, T (bs, fs))
| remove (T (bs, nil)) = remove (T (nil, rev bs))
end
The same (practical) data abstraction is enforced. There is a subtle
technical difference in the types of Queue2.queue and Queue1.queue,
but not one that can be exploited.
Now, there might be compilation reasons why you would prefer opaque
ascription to transparent ascription. For example, with a cutoff
recompilation system, an opaque ascription is a stronger cutoff point than
a transparent ascription. If I were to change Queue1 to
structure Queue1 : QUEUE =
struct
datatype 'a queue = T of {back: 'a list, front: 'a list}
val empty = T {back = nil, front = nil}
fun insert (x, T {back=bs,front=fs}) = T {back =x::bs, front = fs}
exception Empty
fun remove (T {back=nil, front=nil}) = raise Empty
| remove (T {back=bs, front=f::fs}) = (f, T {back=bs, front=fs})
| remove (T {back=bs, front=nil}) = remove (T (back=nil, front=rev bs})
end
then (technically) Queue1.queue has a new type identity, which means that
the augmented signature of Queue1 is different, which means that it may be
necessary to recompile the clients of Queue1 -- despite the fact that they
can't actually observe the change in representation.
If I were to change Queue2 in the same manner, then the opaque ascription
means that the (non-augmented) signature of Queue2 is exactly the same,
and it is not necessary to recompile the clients of Queue2. (Of course,
if you expect your compiler to perform any cross-module inlining, then it
still needs to recompile clients, in order to propagate the code changes.)
Similarly, it may be the case that a compiler represents
datatype t = T of int
with an extra level of indirection, which may be deemed a performance
penalty. So, one would avoid using datatypes, and instead use opaque
ascription to enforce the data abstraction.
Of course, in a whole-program compiler like MLton, neither of these issues
are of concern.
More information about the MLton-user
mailing list