SML: What's the difference between using absty

2020-06-01 05:30发布

问题:

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 make things but keep their implementation details hidden. I can also create a signature of the thing I want to make, and use the :> operator to make a structure adhering to that signature that keeps the implementation details hidden.

Aren't signatures/structures just a more general version of abstypes? What can I do with abstypes that I can't do with signatures/structures? Why would I ever want to use abstype?

Thanks in advance for the help!

As an example:

signature SET = sig
    type set
    val empty: set
    val insert: int * set -> set
    val member: int * set -> bool
end

structure Set :> SET = struct
    type set = int list
    val empty = []
    fun insert(x, s) = x::s
    fun member(x, []) = false
      | member(x, h::t) = (x = h) orelse member(x, t)
end

seems at least as powerful as

abstype AbsSet = absset of int list with
    val empty = absset([])
    fun insert(x, absset(s)) = absset(x::s)
    fun member(x, absset([])) = false
      | member(x, absset(h::t)) = (x = h) orelse member(x, absset(t))
end

回答1:

Why would I ever want to use abstype?

Starting with the easiest, you won't. Atleast I can't come up with one good reason.


Aren't signatures/structures just a more general version of abstypes?

Well, I guess we have to take a look at the history of SML. The opaque (... :> ...) signature matching was not part of SML '90 as explained on this smlnj document about modules 1.3.9. opaque signature matching :>

... whose purpose was to create an "abstract" instance of the signature SIG. This feature was left out of SML '90 for various reasons, but the need for it was real.

I have no idea about the reasoning for not including it, but as far as I know McQueen was the "farther" of the abstype which was part of SML '90 and for some reason wasn't removed in SML '97 (maybe backwards compatibility?)

There is however a fundamentally difference between them, abstype is part of the core language where modules/signatures/functors are part of the module system.


What can I do with abstypes that I can't do with signatures/structures?

I can't come up with anything. However I'm pretty sure it would be easy to construct some example that you can using opaque signature matching and can't do with abstypes.


UPDATE

The page Degrade abstype to derived form from the successor-ml wiki actually contains a tiny informal description about abstype being a leftover.

As with many other, they also refer to the sections of the Defects in the Revised Definition of Standard ML paper which contains details about some "minor" errors/defects in the definition of abstype, although their link being dead. The "Revised Definition of Standard ML" is the definition of SML '97.



回答2:

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.