[MLton] Combining signatures in SML'97

Vesa Karvonen vesa.karvonen at cs.helsinki.fi
Sun Dec 24 22:43:12 PST 2006


Working on the extended basis library I noticed that I have duplicated
many specifications in various signatures.  For example, both ARRAY and
VECTOR have the specs

  val toList : 'a t -> 'a List.t
  val isoList : ('a t, 'a List.t) Iso.t

Likewise ARRAY and BUFFER have the spec

  val duplicate : 'a t -> 'a t

Also, INTEGER, INT_INF, and WORD have the specs

   val isEven : t UnPr.t
   val isOdd : t UnPr.t
   val isZero : t UnPr.t

and there are many more examples.  Instead of duplicating specs and
documentation, I'd like to be able to write signatures for concepts such
as "ORDERED", "SIGNED", and "BOUNDED" once and for all and then create
signatures by combining such concept (or mixin) signatures (using
include).

A common SML convention is to name the main (usually abstract) type of a
signature "t".  Unfortunately, if two SML signatures have a specification
in common, they can not both be included side-by-side.  Thus the "t"
convention effectively prevents combining signatures in SML'97.  It is not
that having a naming convention would be bad.  The problem is that the "t"
convention isn't a good fit to the language.  Indeed, if, as described in

  http://mlton.org/References#RamseyFisherGovereau05,

SML had "An Expressive Language of Signatures" then the convention would
be just fine.  While the "t" convention is not appropriate for concept
signatures, it could be used in other kind of signatures.

So, instead of always using "t" or some artificial variation of "t" like
"also_t", "t'", or "t0", concept signatures need a naming convention that
ensures, as far as possible, that we can combine those signatures when it
makes sense.  A natural convention is to name the types in a concept
signature after the name of the signature.  For example, in a signature
named "ORDERED", the main type would be named "ordered":

signature ORDERED = sig
   type ordered
   val != : ordered BinPr.t
   val <  : ordered BinPr.t
   val <= : ordered BinPr.t
   val == : ordered BinPr.t
   val >  : ordered BinPr.t
   val >= : ordered BinPr.t
   val compare : ordered Cmp.t
   val max : ordered BinOp.t
   val min : ordered BinOp.t
end

In a signature named "SIGNED", the main type would be named "signed":

signature SIGNED = sig
   type signed
   val abs : signed UnOp.t
   val copySign : signed BinOp.t
   val sameSign : signed BinPr.t
   val sign : signed -> Int.t
   val signBit : signed UnPr.t
end

In a signature named "SCANABLE", the main type would be named "scanable":

signature SCANABLE = sig
   type scanable
   val scan : (Char.t, 'a) Reader.t -> (scanable, 'a) Reader.t
end

Likewise, in a signature named "BITWISE_OPERABLE", the main type would be
named, perhaps unsurprisingly, "bitwise_operable":

signature BITWISE_OPERABLE = sig
   type bitwise_operable
   val andb : bitwise_operable BinOp.t
   val notb : bitwise_operable UnOp.t
   val orb : bitwise_operable BinOp.t
   val xorb : bitwise_operable BinOp.t
   val << : bitwise_operable * Word.t -> bitwise_operable
   val ~>> : bitwise_operable * Word.t -> bitwise_operable
end

When multiple types are needed the signature name can be used as a prefix.
For example, in a signature named "FORMATTABLE" one would have two types
named "formattable" (for the main type) and "formattable_format" (for the
other type):

signature FORMATTABLE = sig
   type formattable
   type formattable_format
   val fmt : formattable_format -> formattable -> String.t
end

Note that, thanks to type inference, we rarely need to mention the names
of types except in signatures.

In case we have a variation of a concept, such as "SCANABLE_FROM_FORMAT"
which is a variation of "SCANABLE" we could perhaps make an exception and
use the shorter name as the overlapping value specifications already
prevent including both:

signature SCANABLE_FROM_FORMAT = sig
   type scanable
   type scanable_format
   val scan :
       scanable_format -> (Char.t, 'a) Reader.t -> (scanable, 'a) Reader.t
end

When mechanically combining concept signatures that often occur together,
we could use a convention of concatenating the names of the signatures
with "and" as in "FORMATTABLE_and_SCANABLE_FROM_FORMAT" and
"FORMATTABLE_and_SCANABLE":

signature FORMATTABLE_and_SCANABLE = sig
   include FORMATTABLE
   include SCANABLE where type scanable = formattable
end

signature FORMATTABLE_and_SCANABLE_FROM_FORMAT = sig
   include FORMATTABLE
   include SCANABLE_FROM_FORMAT
      where type scanable = formattable
      where type scanable_format = formattable_format
end

The use of the lowercase "and" is supposed to make it clear that the
signature is a simple combination.  Note that had we used unprefixed type
name "format" in "FORMATTABLE" and "SCANABLE_FROM_FORMAT" we would not
have been able to combine the signatures.

As one can see these naming conventions make it possible to combine
signatures using include.  As further examples, one could have in
signatures "INTEGER", "INT_INF", "REAL", and "WORD" something like

signature INTEGER = sig
   eqtype int
   (* ... *)
   include FORMATTABLE_and_SCANABLE_FROM_FORMAT
      where type formattable = int
      where type formattable_format = StringCvt.radix
   include ORDERED where type ordered = int
   include SIGNED where type signed = int
end

signature INT_INF = sig
   include INTEGER
   (* ... *)
   include BITWISE_OPERABLE where type bitwise_operable = int
end

signature REAL = sig
   type real
   (* ... *)
   include FORMATTABLE_and_SCANABLE
      where type formattable = real
      where type formattable_format = StringCvt.realfmt
   include ORDERED where type ordered = real
   include SIGNED where type signed = real
end

signature WORD = sig
   eqtype word
   (* ... *)
   include BITWISE_OPERABLE where type bitwise_operable = word
   include FORMATTABLE_and_SCANABLE_FROM_FORMAT
      where type formattable = word
      where type formattable_format = StringCvt.radix
   include ORDERED where type ordered = word
end

The main drawback of this convention is that the combined signature (and
any implementing structure) will have many equal types, but that isn't
much of a problem as SML can express type equality without revealing
implementation details.

-Vesa Karvonen




More information about the MLton mailing list