SML: What's the difference between using abstype and using a signature to hide the implementation of a structure?

前端 未结 2 2111
青春惊慌失措
青春惊慌失措 2021-02-07 07:20

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

相关标签:
2条回答
  • 2021-02-07 07:47

    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.

    0 讨论(0)
  • 2021-02-07 08:04

    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.

    0 讨论(0)
提交回复
热议问题