cs 251 fall 2019 cs 251 fall 2019 topics principles of
play

CS 251 Fall 2019 CS 251 Fall 2019 Topics Principles of - PowerPoint PPT Presentation

CS 251 Fall 2019 CS 251 Fall 2019 Topics Principles of Programming Languages Principles of Programming Languages Ben Wood Ben Wood ls is the most Hiding imple lementat ation detai ails important strategy Structures, Signatures,


  1. λ λ CS 251 Fall 2019 CS 251 Fall 2019 Topics Principles of Programming Languages Principles of Programming Languages Ben Wood Ben Wood ls is the most Hiding imple lementat ation detai ails important strategy Structures, Signatures, for writing correct, robust, reusable software. and Abstract Types • ML structures and signatures. • Abstraction for robust library and client+library code. • Abstraction for easy change. • ADTs and functions as data. https://cs.wellesley.edu/~cs251/f19/ 1 Abstract Types 2 Abstract Types structure Name = structure (module) struct bindings end Hiding with functions namespace management and code organization procedural abstraction Can you tell the difference? structure MyMathLib = struct fun double x = x*2 fun fact 0 = 1 fun double x = x+x - double 4; | fact x = x * fact (x-1) val y = 2 fun double x = x*y val it : int = 8 val half_pi = Math.pi / 2 fun double x = let fun help 0 y = y fun doubler x = x * 2 | help x y = end help (x-1) (y+1) in help x x end outside: val facts = List.map MyMathLib.fact [1,3,5,7,9] "Private", but can't be shared among functions. Abstract Types 4 Abstract Types 5

  2. signature NAME = structure Name :> NAME = signature sig binding-types end struct bindings end ascription type for a structure (module) (opaque – will ignore other kinds) List of bindings and their types: Structure must have all bindings with types as declared in signature. variables, type synonyms, datatypes, exceptions signature MATHLIB = sig val fact : int -> int Real power: signature MATHLIB = val half_pi : real Abstraction and Hiding Ab sig val doubler : int -> int val fact : int -> int end val half_pi : real val doubler : int -> int structure MyMathLib :> MATHLIB = end struct fun fact 0 = 1 | fact x = x * fact (x-1) val half_pi = Math.pi / 2 fun doubler x = x * 2 end Abstract Types 6 Abstract Types 7 Abstract Data Type Hiding with signatures type of data and operations on it MyMathLib.doubler is unbound (not in environment) Example: rational numbers supporting add and toString outside module. structure Rational = signature MATHLIB2 = struct sig datatype rational = Whole of int val fact : int -> int | Frac of int*int val half_pi : real exception BadFrac end (* see adts.ml for full code *) structure MyMathLib2 :> MATHLIB2 = fun make_frac (x,y) = ... struct fun add (r1,r2) = ... fun fact 0 = 1 fun toString r = ... | fact x = x * fact (x-1) end val half_pi = Math.pi / 2.0 fun doubler x = x * 2 end Abstract Types 8 Abstract Types 9

  3. Library spec and invariants More on invariants External properties [externally visible gu Our code maintains (and relies) on invariants. guarantees , up to library writer] – Disallow 0 denominators – Return strings in reduced form Maintain: (“4” not “4/1”, “3/2” not “9/6”) – make_frac disallows 0 denominator, removes negative – No infinite loops or exceptions denominator, and reduces result – add assumes invariants on inputs, calls reduce if Implementation invariants [not in external specification] needed – All denominators > 0 – All rational values returned from functions are reduced Rely: – gcd assumes its arguments are non-negative – add uses math properties to avoid calling reduce Signatures help en enforce ce internal invariants. – toString assumes its argument is in reduced form Abstract Types 10 Abstract Types 11 A first signature Problem: clients can violate invariants Create values of type Rational.rational directly. Helper functions gcd and reduce not visible outside module. signature RATIONAL_OPEN = signature RATIONAL_OPEN = Attempt #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 val toString : rational -> string end Rational.Frac(1,0) structure Rational :> RATIONAL_OPEN = ... Rational.Frac(3,~2) Rational.Frac(9,6) Abstract Types 12 Abstract Types 13

  4. Solution: hide more! Abstract the type! (Really Big Deal!) Client can pass them around, but can AD ADT must hide concrete type definition so clients Type rational exists, manipulate them only through module. but representation absolutely hidden. cannot cr ca create invariant-vio iola latin ing valu lues of ty type. signature RATIONAL = Success! (#3) Attempt #2 signature RATIONAL_WRONG = sig sig Only way to make 1 st rational . type rational exception BadFrac exception BadFrac val make_frac : int * int -> rational val make_frac : int * int -> rational Only operations val add : rational * rational -> rational val add : rational * rational -> rational on rational . val toString : rational -> string val toString : rational -> string end end structure Rational :> RATIONAL_WRONG = ... structure Rational :> RATIONAL = ... Too far: type rational is not known to exist! Module controls all operations with rational , so client cannot violate invariants. Abstract Types 14 Abstract Types 15 Abstract Data Type Abstract Data Types: two key tools Abstract type of data + operations on it Outside of implementation: Powerful ways to use signatures for hiding: • Values of type rational can be cr created and manipulated only thr hrough h ADT operations. 1. 1. De Deny bi bindings exist. • Co Conc ncrete e rep epres esent entation n of values of type rational Especially val bindings, fun bindings, constructors. is is ab abso solutely hi hidden. signature RATIONAL = 2. Ma 2. Make types abs bstract ct. sig 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 = ... Abstract Types 16 Abstract Types 17

  5. A cute twist Signature matching rules structure Struct :> SIG type-checks if and only if al all Exposing the Whole constructor is no problem. of the following hold: Expose it as a function: 1. Every non-abstract type in SIG is provided in Struct , as specified – Still hiding the rest of the datatype – Still does not allow using Whole as a pattern 2. Every abstract type in SIG is provided in Struct in some way signature RATIONAL_WHOLE = 3. Every val-binding in SIG is provided in Struct , possibly with a sig more general and/or less abstract internal type type rational exception BadFrac 4. Every exception in SIG is provided in Struct. val Whole : int -> rational val make_frac : int * int -> rational Struct can have more bindings (implicit in above rules) val add : rational * rational -> rational val toString : rational -> string end Abstract Types 18 Abstract Types 19 Allow different implementations to be PairRational (alternative concrete type) equivalent A key purpose of abstraction: structure PairRational = – No client can tell which you are using struct – Can improve/replace/choose implementations later type rational = int * int – Easier with more abstract signatures (reveal only what you must) exception BadFrac UnreducedRational in adts.sml . fun make_frac (x,y) = … fun Whole i = (i,1) (* for RATIONAL_WHOLE *) – Same concrete datatype. fun add ((a,b)(c,d)) = (a*d + b*c, b*d) – Di Different invariant : reduce fractions only in toString . fun toString r = ... (* reduce at last minute *) – Equivalent under RATIONAL and RATIONAL_WHOLE , but not under RATIONAL_OPEN . end PairRational in adts.sml . – Di Different concrete da datatype. – Equivalent under RATIONAL and RATIONAL_WHOLE , but cannot ascribe RATIONAL_OPEN . Abstract Types 20 Abstract Types 21

Recommend


More recommend