15-150 Fall 2020 Lecture 15
Stephen Brookes
Modular programming (part 1)
15-150 Fall 2020 Lecture 15 Modular programming (part 1) Stephen - - PowerPoint PPT Presentation
15-150 Fall 2020 Lecture 15 Modular programming (part 1) Stephen Brookes problem of the day Write an ML function factzeros : int -> int such that for all n>0, 5! = 120 factzeros n = factzeros 5 = 1 the number of zeros on the end of
Stephen Brookes
Modular programming (part 1)
Write an ML function factzeros : int -> int such that for all n>0, factzeros n = the number of zeros
5! = 120 factzeros 5 = 1
collection of small components
Motivation: to facilitate separate development
signature ORDERED = sig type t val compare : t * t -> order end
“ordered types”
structure Integers : ORDERED = struct type t = int fun compare (x, y) = if x<y then LESS else if y<x then GREATER else EQUAL end
integers, with usual order
Ordered types are everywhere
helper functions, type implementation
transparently or opaquely…
structure Integers : ORDERED = struct …
: means transparent
can organize code into separate fragments
a signature + a structure that implements it
different programmer teams, or assembled in any order, then combined
Signatures
Structures
Functors
A signature is a collection of names with specified types signature SIGNAME = sig end . . .
names for types, functions, exceptions
signature ARITH = sig type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer end
A type named integer A function rep : int -> integer A function display : integer -> string A function add : integer * integer -> integer A function mult : integer * integer -> integer
doesn’t create any types, values, exceptions…
will create instances of the types, values and exceptions in the signature
may have many implementations
structure Ints = struct type integer = int fun rep n = n fun display n = Int.toString n fun add(x, y) = x + y fun mult(x, y) = x * y end
This structure implements ARITH and defines integer as the type int
signature ARITH = sig type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer end
structure Ints : sig type integer = int val rep : 'a -> 'a val display : int -> string val add : int * int -> int val mult : int * int -> int end
ML says
signature ARITH = sig type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer end
structure Ints : sig type integer = int val rep : ’a -> ’a val display : int -> string val add : int * int -> int val mult : int * int -> int end
signature ARITH = sig type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer end
rep is OK because integer = int and int -> integer is an instance of ’a -> ’a
structure Ints : ARITH = struct type integer = int fun rep n = n fun display n = Int.toString n fun add(x, y) = x + y fun mult(x, y) = x * y end
structure Ints : ARITH
ML says
ascribing the signature ARITH constrains what the structure provides
(transparent)
Ints must have a type definition
Ints must have a suitable declaration Ints : ARITH same name, at least as general type these rules must be obeyed by implementers
Ints : ARITH
with types at least as general as required, given that integer is int
add : integer * integer -> integer
add : int * int -> int rep : int -> integer rep : int -> int
> Error: unmatched value specification: radius
structure Square : CIRCLE = struct val side = 3.0 val center = (0.0, 0.0) end signature CIRCLE = sig val radius : real val center : real * real end
Ints : ARITH
and can do arithmetic on values of type integer
this rule is imposed on all clients or users 40 + rep 2 =>* 42 : int
type integer = int val rep : int -> integer val add : integer * integer -> integer val mult : integer * integer -> integer val display : integer -> string
the types and values in its signature ML says
val it = 5 : integer
struct type integer = int; … … end
type integer = int …….
see type information specified inside the structure
type integer = int
fun double(x:integer):integer = mult(x, 2)
ML says val double = fn - : integer -> integer
see type information specified inside the structure
type integer = int
fun double(x:integer):integer = mult(x, 2)
ML says val double = fn - : integer -> integer
This may seem fine, but knowledge is power, so be careful
see type information specified inside the structure
type integer = int
fun double(x:integer):integer = mult(x, 2)
ML says val double = fn - : integer -> integer
This may seem fine, but knowledge is power, so be careful
We’ll soon see an example where it makes better sense to hide the type implementation from users
Write a function fact : int -> integer such that for n ≥ 0, fact n = an integer value representing n! Available for use: rep : int -> integer add : integer * integer -> integer mult : integer * integer -> integer display : integer -> string for ARITH
if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer
val it = 6 : integer
this should work for ANY structure that implements ARITH
fun fact(n:int):integer = if n=0 then rep 1 else mult(rep n, fact(n-1)) raises Overflow : integer : integer fact 100 fact 3 =>* 6 : int
type integer = int val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer
Use the structure name to disambiguate The built-in ML structures Int and String both define compare
fun fact(n:int): Ints.integer = if n=0 then Ints.rep 1 else Ints.mult(Ints.rep n, fact(n-1)) raises Overflow fact 100 fact 3 =>* 6 : int
type Ints.integer = int
this is allowed, but a mistake
structure Ints : ARITH = ...
We ascribed the signature transparently transparency allows users to exploit the fact that integer is int and execute dangerous code : int : integer val fact = fn - : int -> integer fun fact(n:int):integer = if n=0 then ~1 else mult(rep n, fact(n-1));
if n=0 then ~1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer
val it = ~6 : integer
if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer
val it = 6 : integer
structure Ints :> ARITH = ...
We can ascribe the signature opaquely using :> : int : integer Error: types of if branches do not agree fun fact(n:int):integer = if n=0 then ~1 else mult(rep n, fact(n-1))
the fact that integer is int
if n=0 then ~1 else mult(rep n, fact(n-1)); Error: types of if branches do not agree
if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer
val it = 6 : integer
GOOD!
Ints must have a type definition
Ints must have a suitable declaration Ints :> ARITH same name, at least as general type (same as with transparent ascription)
and value bindings named in ARITH
Ints :> ARITH these rules are imposed on all clients or users
but cannot do arithmetic on values of type integer
40 + rep 2 is not well-typed!
structure Ints :> ARITH = …
type integer val add : integer * integer -> integer val mult : integer * integer -> integer val rep : int -> integer val display : integer -> string
val it = - : integer
ML says
NOT THIS!
val it = 5 : integer
users cannot see inside values of type integer
fun double(x:integer):integer = mult(x, 2)
ML says val double = fn - : integer -> integer ML says ….. Error: operator and operand do not agree
ML won’t let you break the abstraction barrier
Ints isn’t the only way... Let’s find a way to deal with larger integers, like 100!
fact 100; uncaught exception Overflow [overflow] raised at: <file stdIn>
if n=0 then rep 1 else mult(rep n, fact(n-1));
(with least significant digit first)
(with least significant digit first)
(with least significant digit first)
1
(with least significant digit first)
1 1
(with least significant digit first)
1 1 4
(with least significant digit first)
1 1 4 269 + 148 = 417
structure Dec : ARITH = struct type digit = int type integer = digit list fun rep 0 = [ ] | rep n = (n mod 10) :: rep(n div 10)
fun display [ ] = "0" | display L = foldl (fn (d, s) => Int.toString d ^ s) "" L
........
(* carry : digit * integer -> integer *) fun carry (0, ps) = ps | carry (c, [ ]) = [c] | carry (c, p::ps) = ((p+c) mod 10) :: carry ((p+c) div 10, ps) fun add ([ ], qs) = qs | add (ps, [ ]) = ps | add (p::ps, q::qs) = ((p+q) mod 10) :: carry ((p+q) div 10, add(ps, qs))
.... continued ...
(* times : digit * integer -> integer *) fun times (0, qs) = [ ] | times (p, [ ]) = [ ] | times (p, q::qs) = ((p * q) mod 10) :: carry ((p * q) div 10, times(p, qs))
fun mult ([ ], _) = [ ] | mult (_, [ ]) = [ ] | mult (p::ps, qs) = add (times(p, qs), 0 :: mult (ps, qs)) end
.... continued ....
structure Dec : ARITH = struct type digit = int type integer = digit list fun rep 0 = [ ] | rep n = (n mod 10) :: rep (n div 10); fun carry (0, ps) = ps | ... fun add ([ ], qs) = qs | .... fun times(0, qs) = [ ] | ....
fun mult([ ], _) = [ ] | .... fun display L = foldl (fn (d, s) => Int.toString d ^ s) "" L end;
so not visible to users of Dec
carry : digit * integer -> integer times : digit * integer -> integer
fun fact(n:int):integer = if n=0 then rep 1 else mult(rep n, fact(n-1)) : integer : integer fact 100 =>* [0,0,0,0,…] : int list fact 3 =>* [6] : int list
type integer = int list
display(fact 100) = "9332621544394415268169923885626670049071 59682643816214685929638952175999932299156 08941463976156518286253697920827223758251 185210916864000000000000000000000000"
24 trailing zeros
that implement the same signature
values of type int list built from decimal digits
are decimal Ints Dec ARITH
from decimal arguments
rep, carry, add, mult, … is a decimal list and represents an integer
The ARITH functions defined in Dec are arithmetically accurate
then add(xs, ys) represents x+y
then mult(xs, ys) represents x*y
collection of operations (“addition,…”)
using values that satisfy an invariant
abstract value, and the functions in Dec correctly implement the operations, when used on values satisfying the invariant
carry(c, ps) evaluates to a decimal list
then add(ps, qs) evaluates to a decimal list Induction on n Induction on length(ps) Induction on length(ps) * length(qs)
the integer written (in decimal notation) as dn…d1d0 fun eval [ ] = 0 | eval (d::R) = d + 10 * (eval R)
eval : int list -> int
REQUIRES L is a decimal list ENSURES eval L = the integer represented by L
carry(c, ps) represents c+p
then add(ps, qs) represents p+q Induction on n Induction on length(ps) Induction on length(ps) * length(qs)
If L represents n, display L evaluates to the string for n If ps and qs represent p and q, then mult(ps, qs) represents p*q