SML Modules and Abstract Data Types (Adts)
Total Page:16
File Type:pdf, Size:1020Kb
SML Modules and Overview of Modules and ADTs Hiding implementa-on details is the most important strategy Abstract Data Types (ADTs) for wriCng correct, robust, reusable soEware. Topics: These slides are lightly edited versions of Ben Wood’s Fall ‘15 slides, some of which are based on Dan Grossman’s material from the University of Washington. • ML structures and signatures. • AbstracCon for robust library and client+library code. • AbstracCon for easy change. • ADTs and funcCons as data. CS251 Programming Languages Spring 2019, Lyn Turbak Department of Computer Science Wellesley College SML Modules and ADTS 2 structure Name = struct bindings end Hiding with funcLons structure (module) procedural abstrac.on namespace management and code organizaLon structure MyMathLib = Hiding implementaCon details is the most important strategy for struct wriCng correct, robust, reusable soEware. fun fact 0 = 1 | fact x = x * fact (x-1) fun double x = x*2 Can you tell the difference? fun double x = x+x val half_pi = Math.pi / 2 val y = 2 - double 4; fun double x = x*y fun doubler x = x * 2 fun double x = val it : int = 8 let fun help 0 y = y val twelve = doubler (fact 3) | help x y = end help (x-1) (y+1) in help x x end outside: “Private” top-level funcCons would also be nice... val facts = List.map MyMathLib.fact • share a "private" helper funcCon [1,4,MyMathLib.doubler 3, MyMathLib.twelve] SML Modules and ADTS 3 SML Modules and ADTS 4 signature NAME = structure Name :> NAME = sig binding-types end ascripLon struct bindings end signature (opaque – will ignore other kinds) type for a structure (module) Ascribing a signature to a structure • Structure must have all bindings with types as declared in signature. List of bindings and their types: signature MATHLIB = variables (incl. funcCons), type synonyms, datatypes, excepCons sig Separate from specific structure. val fact : int -> int Real power: val half_pi : real val doubler : int -> int Abstrac-on and Hiding signature MATHLIB = val twelve : int sig end val fact : int -> int val half_pi : real structure MyMathLib :> MATHLIB = val doubler : int -> int struct val twelve : int fun fact 0 = 1 end | fact x = x * fact (x-1) val half_pi = Math.pi / 2 fun doubler x = x * 2 val twelve = doubler (fact 3) SML Modules and ADTS 5 end SML Modules and ADTS 6 Hiding with signatures Abstract Data Type type of data and operaLons on it MyMathLib.doubler unbound (not in environment) outside module. Example: raConal numbers supporCng add and toString signature MATHLIB2 = sig structure Rational = val fact : int -> int struct val half_pi : real datatype rational = Whole of int val twelve : int | Frac of int*int end exception BadFrac structure MyMathLib2 :> MATHLIB2 = (* see rationals.sml for full code *) struct fun fact 0 = 1 fun make_frac (x,y) = ... | fact x = x * fact (x-1) fun add (r1,r2) = ... val half_pi = Math.pi / 2.0 fun toString r = ... fun doubler x = x * 2 end fun twelve = doubler (fact 3) end SML Modules and ADTS 7 SML Modules and ADTS 8 Library spec and invariants More on invariants External properCes [externally visible guarantees, up to library writer] • Disallow denominators of 0 Our code maintains (and relies) on invariants. • Return strings in reduced form (“4” not “4/1”, “3/2” not “9/6”) • No infinite loops or excepCons Maintain: • make_frac disallows 0 denominator, removes negaCve denominator, and ImplementaCon invariants [not in external specifica9on] reduces result • All denominators > 0 • add assumes invariants on inputs, calls reduce if needed • All rational values returned from funcCons are reduced Rely: • gcd assumes its arguments are non-negaCve Signatures help enforce internal invariants. • add uses math properCes to avoid calling reduce • toString assumes its argument is in reduced form SML Modules and ADTS 9 SML Modules and ADTS 10 A first signature Problem: clients can violate invariants With what we know so far, this signature makes sense: • Helper funcCons gcd and reduce not visible outside the module. Create values of type Rational.rational directly. signature RATIONAL_CONCRETE = signature RATIONAL_CONCRETE = A7empt #1 sig sig datatype rational = Whole of int datatype rational = Whole of int | Frac of int*int | Frac of int*int ... exception BadFrac end val make_frac : int * int -> rational val add : rational * rational -> rational Rational.Frac(1,0) val toString : rational -> string Rational.Frac(3,~2) end Rational.Frac(40,32) structure Rational :> RATIONAL_OPEN = ... SML Modules and ADTS 11 SML Modules and ADTS 12 SoluLon: hide more! Abstract the type! (Really Big Deal!) Client can pass them around, but can ADT must hide concrete type defini5on so clients cannot Type rational exists, manipulate them only through module. create invariant-viola5ng values of type directly. but representa-on absolutely hidden. signature RATIONAL = Success! (#3) This agempt goes too far: type rational is not known to exist sig type rational Only way to make 1st rational. exception BadFrac signature RATIONAL_WRONG = A7empt #2 Only opera-ons val make_frac : int * int -> rational sig on rational. val add : rational * rational -> rational exception BadFrac val toString : rational -> string val make_frac : int * int -> rational end val add : rational * rational -> rational structure Rational :> RATIONAL = ... val toString : rational -> string end structure Rational :> RATIONAL_WRONG = ... Module controls all opera-ons with rational, so client cannot violate invariants. SML Modules and ADTS 13 SML Modules and ADTS 14 Abstract Data Type Abstract type of data + operaLons on it Abstract Data Types: two key tools Outside of implementaCon: • Values of type rational can be Powerful ways to use signatures for hiding: created and manipulated only through ADT opera-ons. • Concrete representa-on of values of type rational is absolutely hidden. 1. Deny bindings exist. Especially val bindings, fun bindings, constructors. signature RATIONAL = sig 2. Make types abstract. type rational Clients cannot create or inspect values of the type directly. exception BadFrac val make_frac : int * int -> rational val add : rational * rational -> rational val toString : rational -> string end structure Rational :> RATIONAL = ... SML Modules and ADTS 15 SML Modules and ADTS 16 A cute twist Signature matching rules In our example, exposing the Whole constructor is no problem structure Struct :> SIG type-checks if and only if: In SML we can expose it as a funcCon since the datatype binding in the • Every non-abstract type in SIG is provided in Struct, as specified module does create such a funcCon • SCll hiding the rest of the datatype • Every abstract type in SIG is provided in Struct in some way • SCll does not allow using Whole as a pagern • Can be a datatype or a type synonym signature RATIONAL_WHOLE = • Every val-binding in SIG is provided in Struct, possibly with a more sig general and/or less abstract internal type type rational • 'a list -> int more general than string list -> int • example soon exception BadFrac val Whole : int -> rational • Every excepCon in SIG is provided in Struct. val make_frac : int * int -> rational val add : rational * rational -> rational val toString : rational -> string Of course Struct can have more bindings (implicit in above rules) end SML Modules and ADTS 17 SML Modules and ADTS 18 PairRaLonal (alternaLve concrete type) Allow different implementa.ons to be equivalent A key purpose of abstracCon: structure PairRational = • No client can tell which you are using struct • Can improve/replace/choose implementaCons later type rational = int * int • Easier with more abstract signatures (reveal only what you must) exception BadFrac fun make_frac (x,y) = … UnreducedRational in adts.sml. fun Whole i = (i,1) (* for RATIONAL_WHOLE *) • Same concrete datatype. fun add ((a,b)(c,d)) = (a*d + b*c, b*d) • Different invariant: reduce fracCons only in toString. fun toString r = ... (* reduce at last minute *) • Equivalent under RATIONAL and RATIONAL_WHOLE, end but not under RATIONAL_CONCRETE. PairRational in adts.sml. • Different concrete datatype. • Equivalent under RATIONAL and RATIONAL_WHOLE, but cannot ascribe RATIONAL_CONCRETE. SML Modules and ADTS 19 SML Modules and ADTS 20 Some interesLng details Cannot mix and match module bindings • Internally make_frac has type int * int -> int * int, Modules with the same signatures sCll define different types externally int * int -> rational • Client cannot tell if we return argument unchanged These do not type-check: • Rational.toString(UnreducedRational.make_frac(9,6)) • Internally Whole has type 'a -> 'a * int • PairRational.toString(UnreducedRational.make_frac(9,6)) externally int -> rational • specialize 'a to int Crucial for type system and module properLes: • abstract int * int to rational • Type-checker just figures it out • Different modules have different internal invariants! • ... and different type definiLons: • Whole cannot have types 'a -> int * int • UnreducedRational.rational looks like Rational.rational, but or 'a -> rational (must specialize all 'a uses) clients and the type-checker do not know that • PairRational.rational is int*int not a datatype! SML Modules and ADTS 21 SML Modules and ADTS 22 Double Ccks mean a Is an equality Set ADT (set.sml) type (can compare elts with =) Side Note: Equality Types Double-9ck types like range over so-called equality types, which are types over signature SET = Common idiom: if module provides ''a which the polymorphic equality operator = is defined. sig one externally visible type, name it t. type ''a t Then outside references are Set.t. Sadly, the semanCcs of IEEE 754 floaCng point arithmeCc standard prevents the real val empty : ''a t type from being an equality type. It includes Nan (not-a-number) values that represent val singleton : ''a -> ''a t the results of certain operaCons, such as subtracCng posiCve infinity from itself. According to the IEEE standard, tesCng two Nan values for equality must return false, val isEmpty : ''a t -> bool but that would break the reflexivity property that is required for an equality type (i.e., val size : ''a t -> int for any value v in an equality type, v = v must be true).