I\'ve done a little work in SML in the past, but I\'m now starting to get to the more interesting parts.
Using the abstype...with...end
construct, I can ma
The one big reason why abstype
is still required is for realistic applications is toplevel pretty printing. This refers to the intersection of SML/NJ and Poly/ML -- I don't know how Mlton works in this respect (it does not have a proper toplevel).
The task is simple: define an abstract datatype (one that does not leak equality) and provide a toplevel pretty printer for it. The only (quasi-portable) answer that I know uses plain-old abstype with SML'90-style non-opaque signature matching:
structure A1:
sig
type t val a: t val b: t -> t val print: t -> string
end =
struct
abstype t = A of int
with
val a = A 42
fun b (A i) = A (i + 1)
fun print (A i) = "{" ^ Int.toString i ^ "}[1]"
end
end;
(* works for Poly/ML 5.3, 5.4, 5.5:
PolyML.addPrettyPrinter (fn depth => fn pretty => fn x => PolyML.PrettyString (A1.print x));
*)
(* works for SML/NJ 110.xx:
CompilerPPTable.install_pp ["A1", "t"] (fn pps => fn x => PrettyPrint.string pps (A1.print x));
*)
A1.a
should print {42}[1]
in this funny example -- the compiler specific lines need to be un-commented. This is definitely outside the SML'97 standard, or later attempts at ML'2000 and beyond, but it works both for SML/NJ and Poly/ML, as they are still available today. In some sense you see some older SML'90 and pre-SML culture shining through, even a bit of LISP toplevel hacking. (The above post-ludes to the structure definition can be turned into funny wrappers that invoke the SML toplevel at compile time in a way that works for both, thus making the sources appear portable.)
Note that for applications like Isabelle, HOL4, ProofPower, toplevel ML pretty printing is indispensible, whatever SML standard writers might say.
Here are two more versions that are more in line with SML'97 and opaque signature matching :>
but fail to work uniformly:
structure A2 :>
sig
type t val a: t val b: t -> t val print: t -> string
end =
struct
datatype t = A of int
val a = A 42
fun b (A i) = A (i + 1)
fun print (A i) = "{" ^ Int.toString i ^ "}[2]"
(* works, but non-portable:
val _ =
PolyML.addPrettyPrinter (fn depth => fn pretty => fn x => PolyML.PrettyString (print x))
*)
(* does not work (scope problem -- no pp):
val _ =
CompilerPPTable.install_pp ["A2", "t"] (fn pps => fn x => PrettyPrint.string pps (A2.print x));
*)
end;
(* does not work (no pp):
PolyML.addPrettyPrinter (fn depth => fn pretty => fn x => PolyML.PrettyString (A2.print x));
*)
(* does not work (no pp):
CompilerPPTable.install_pp ["A2", "t"] (fn pps => fn x => PrettyPrint.string pps (A2.print x));
*)
structure A3 :>
sig
type t val a: t val b: t -> t val print: t -> string
end =
struct
type t = int
val a = 42
fun b i = i + 1
fun print i = "{" ^ Int.toString i ^ "}[3]"
(* does not work (overrides pp for int):
val _ =
PolyML.addPrettyPrinter (fn depth => fn pretty => fn x => PolyML.PrettyString (print x))
*)
(* does not work (scope problem -- no pp)
val _ = CompilerPPTable.install_pp ["A2", "t"] (fn pps => fn x => PrettyPrint.string pps (A2.print x));
*)
end;
(* works:
PolyML.addPrettyPrinter (fn depth => fn pretty => fn x => PolyML.PrettyString (A3.print x));
*)
(* does not work (no pp):
CompilerPPTable.install_pp ["A3", "t"] (fn pps => fn x => PrettyPrint.string pps (A3.print x));
*)
I hope that I've got all the odd cases right. The "no pp" sitation is different for different SMLs: Poly/ML prints the original representation, while SML/NJ prints nothing (just a dash as place holder). The "untagged" opaque type is particularly nasty: in Poly/ML it will override the pretty-printer for int, but for SML/NJ it does just nothing, which is also bad.