15-150 Fall 2020 Lecture 15 Modular programming (part 1) Stephen - - PowerPoint PPT Presentation

15 150 fall 2020 lecture 15
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

15-150 Fall 2020 Lecture 15

Stephen Brookes

Modular programming (part 1)

slide-2
SLIDE 2

problem of the day

Write an ML function factzeros : int -> int such that for all n>0, factzeros n = the number of zeros

  • n the end of n! (in decimal)

5! = 120 factzeros 5 = 1

slide-3
SLIDE 3

modularity principle

  • A large program should be organized as a

collection of small components

  • manageable size
  • easy to maintain
  • Give an interface for each component
  • others should rely only on this

Motivation: to facilitate separate development

slide-4
SLIDE 4

advantages

  • Identify and isolate common situations

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

slide-5
SLIDE 5

advantages

  • We may want to hide internal details

helper functions, type implementation

  • Do this by leaving them out of the signature
  • r by ascribing the signature opaquely
  • You can ascribe a signature to a structure

transparently or opaquely…

structure Integers : ORDERED = struct …

: means transparent

slide-6
SLIDE 6

advantages

  • With well chosen signatures,

can organize code into separate fragments

  • A “module” is a combination of

a signature + a structure that implements it

  • Modules can be designed separately by

different programmer teams, or assembled in any order, then combined

slide-7
SLIDE 7

language support

Signatures

  • interfaces

Structures

  • implementations

Functors

  • ways to combine structures...
slide-8
SLIDE 8

signatures

A signature is a collection of names with specified types signature SIGNAME = sig end . . .

names for types, functions, exceptions

slide-9
SLIDE 9

a signature

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

slide-10
SLIDE 10

just an interface

  • Just declaring this signature

doesn’t create any types, values, exceptions…

  • To implement it we must define a structure
  • Defining a structure with this signature

will create instances of the types, values and exceptions in the signature

  • ne signature

may have many implementations

slide-11
SLIDE 11

an implementation

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

slide-12
SLIDE 12

result

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

whoa!

signature ARITH = sig type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer end

slide-13
SLIDE 13

Ints implements ARITH

  • a type named integer
  • values rep, display, add, mult
  • f types consistent with ARITH

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

slide-14
SLIDE 14

ascription

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)

slide-15
SLIDE 15

ascription requirements

  • For every type named in ARITH,

Ints must have a type definition

  • For every value named in ARITH,

Ints must have a suitable declaration Ints : ARITH same name, at least as general type these rules must be obeyed by implementers

slide-16
SLIDE 16

ascription requirements

Ints : ARITH

  • Ints defines integer as int
  • Ints declares the values named in 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

slide-17
SLIDE 17

what can go wrong

> 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

slide-18
SLIDE 18

ascription constraints

  • Users of Ints can only use data named in ARITH

Ints : ARITH

  • Users of Ints “know” that integer is int
  • Users of Ints can call rep, add, mult, display

and can do arithmetic on values of type integer

this rule is imposed on all clients or users 40 + rep 2 =>* 42 : int

slide-19
SLIDE 19
  • pen
  • open Ints;
  • pening Ints

type integer = int val rep : int -> integer val add : integer * integer -> integer val mult : integer * integer -> integer val display : integer -> string

  • pening a structure reveals

the types and values in its signature ML says

  • add(rep 2, rep 3);

val it = 5 : integer

slide-20
SLIDE 20

transparency

  • structure Ints : ARITH =

struct type integer = int; … … end

  • open Ints;
  • pening Ints

type integer = int …….

slide-21
SLIDE 21

transparency

  • Users of a structure with a transparent signature can

see type information specified inside the structure

  • The REPL will allow things like

type integer = int

fun double(x:integer):integer = mult(x, 2)

ML says val double = fn - : integer -> integer

slide-22
SLIDE 22

transparency

  • Users of a structure with a transparent signature can

see type information specified inside the structure

  • The REPL will allow things like

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

slide-23
SLIDE 23

transparency

  • Users of a structure with a transparent signature can

see type information specified inside the structure

  • The REPL will allow things like

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

slide-24
SLIDE 24

using a structure

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

slide-25
SLIDE 25

factorial implementation

  • fun fact(n:int):integer =

if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer

  • fact 3;

val it = 6 : integer

this should work for ANY structure that implements ARITH

slide-26
SLIDE 26

using Ints

  • pen Ints

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

slide-27
SLIDE 27

qualified names

  • Int.compare : int * int -> order
  • String.compare : string * string -> order

Use the structure name to disambiguate The built-in ML structures Int and String both define compare

slide-28
SLIDE 28

using qualified names

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

slide-29
SLIDE 29

this is allowed, but a mistake

transparency

structure Ints : ARITH = ...

  • pen Ints;

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));

slide-30
SLIDE 30

transparency in action

  • fun fact(n:int):integer =

if n=0 then ~1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer

  • fact 3;

val it = ~6 : integer

  • fun fact(n:int):integer =

if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer

  • fact 3;

val it = 6 : integer

GOOD!

BAD!!!!

slide-31
SLIDE 31
  • pacity

structure Ints :> ARITH = ...

  • pen Ints;

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))

  • pacity prevents users from exploiting

the fact that integer is int

slide-32
SLIDE 32
  • pacity in action
  • fun fact(n:int):integer =

if n=0 then ~1 else mult(rep n, fact(n-1)); Error: types of if branches do not agree

  • fun fact(n:int):integer =

if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer

  • fact 3;

val it = 6 : integer

GOOD!

GOOD!

slide-33
SLIDE 33

ascription requirements

  • For every type named in ARITH,

Ints must have a type definition

  • For every value named in ARITH,

Ints must have a suitable declaration Ints :> ARITH same name, at least as general type (same as with transparent ascription)

slide-34
SLIDE 34

ascription constraints

  • Users of Ints can only use the type names

and value bindings named in ARITH

  • Users of Ints cannot “see inside” values
  • f the types named in ARITH

Ints :> ARITH these rules are imposed on all clients or users

  • Users of Ints do not “know” that integer is int
  • Users of Ints can call rep, add, mult, display

but cannot do arithmetic on values of type integer

40 + rep 2 is not well-typed!

slide-35
SLIDE 35
  • paque use

structure Ints :> ARITH = …

  • pen Ints;
  • pening Ints

type integer val add : integer * integer -> integer val mult : integer * integer -> integer val rep : int -> integer val display : integer -> string

  • add(rep 2, rep 3);

val it = - : integer

ML says

NOT THIS!

  • add(rep 2, rep 3);

val it = 5 : integer

users cannot see inside values of type integer

slide-36
SLIDE 36
  • paque vs transparent
  • With transparent ascription
  • With opaque ascription

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

slide-37
SLIDE 37

implementing ARITH

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>

  • fun fact(n:int):integer =

if n=0 then rep 1 else mult(rep n, fact(n-1));

slide-38
SLIDE 38

the plan

  • Represent an integer as a list of decimal digits

(with least significant digit first)

  • Implement digitwise addition and multiplication
  • grade school algorithms
  • using div 10, mod 10, carry propagation
  • Use Int.toString to produce a string
slide-39
SLIDE 39

the plan

  • Represent an integer as a list of decimal digits

(with least significant digit first)

  • Implement digitwise addition and multiplication
  • grade school algorithms
  • using div 10, mod 10, carry propagation
  • Use Int.toString to produce a string
slide-40
SLIDE 40

the plan

  • Represent an integer as a list of decimal digits

(with least significant digit first)

  • Implement digitwise addition and multiplication
  • grade school algorithms
  • using div 10, mod 10, carry propagation
  • Use Int.toString to produce a string

1

slide-41
SLIDE 41

the plan

  • Represent an integer as a list of decimal digits

(with least significant digit first)

  • Implement digitwise addition and multiplication
  • grade school algorithms
  • using div 10, mod 10, carry propagation
  • Use Int.toString to produce a string

1 1

slide-42
SLIDE 42

the plan

  • Represent an integer as a list of decimal digits

(with least significant digit first)

  • Implement digitwise addition and multiplication
  • grade school algorithms
  • using div 10, mod 10, carry propagation
  • Use Int.toString to produce a string

1 1 4

slide-43
SLIDE 43

the plan

  • Represent an integer as a list of decimal digits

(with least significant digit first)

  • Implement digitwise addition and multiplication
  • grade school algorithms
  • using div 10, mod 10, carry propagation
  • Use Int.toString to produce a string

1 1 4 269 + 148 = 417

slide-44
SLIDE 44

decimal digits

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

........

slide-45
SLIDE 45

decimal digits

(* 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 ...

slide-46
SLIDE 46

decimal digits

(* 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 ....

slide-47
SLIDE 47

all together

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;

slide-48
SLIDE 48

implements

  • Dec implements the signature ARITH
  • It’s OK to define extra data, such as
  • These are not in the signature,

so not visible to users of Dec

  • The type integer is int list
  • The only relevant lists contain decimal digits

carry : digit * integer -> integer times : digit * integer -> integer

slide-49
SLIDE 49

sanity check

  • We saw that 269 + 148 = 417
  • Check that add ([9,6,2], [8,4,1]) = [7,1,4]
slide-50
SLIDE 50

using Dec

  • pen Dec

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

slide-51
SLIDE 51

results

display(fact 100) = "9332621544394415268169923885626670049071 59682643816214685929638952175999932299156 08941463976156518286253697920827223758251 185210916864000000000000000000000000"

24 trailing zeros

slide-52
SLIDE 52

reflection

  • We have given two different structures

that implement the same signature

  • The Dec implementation uses

values of type int list built from decimal digits

  • All lists constructible from ARITH functions

are decimal Ints Dec ARITH

slide-53
SLIDE 53

importance

  • A decimal list represents an integer
  • rep produces a decimal list from an int
  • carry, add, mult, … produce a decimal list

from decimal arguments

  • So every value constructible from

rep, carry, add, mult, … is a decimal list and represents an integer

slide-54
SLIDE 54

correctness

The ARITH functions defined in Dec are arithmetically accurate

  • rep n = a decimal list representing n
  • If xs represents x and ys represents y,

then add(xs, ys) represents x+y

  • If xs represents x and ys represents y,

then mult(xs, ys) represents x*y

slide-55
SLIDE 55

key concepts

  • ARITH is a signature for an abstract data type
  • An abstract type (“integers”) with a limited

collection of operations (“addition,…”)

  • Dec implements the abstract type,

using values that satisfy an invariant

  • Values satisfying the invariant represent an

abstract value, and the functions in Dec correctly implement the operations, when used on values satisfying the invariant

slide-56
SLIDE 56

invariance

  • For n ≥ 0, rep n evaluates to a decimal list
  • If 0 ≤ c ≤ 9 and ps is a decimal list, then

carry(c, ps) evaluates to a decimal list

  • If ps and qs are decimal lists,

then add(ps, qs) evaluates to a decimal list Induction on n Induction on length(ps) Induction on length(ps) * length(qs)

slide-57
SLIDE 57

representation

  • A decimal list [d0,d1,…,dn] represents

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

slide-58
SLIDE 58

accuracy

  • For n ≥ 0, rep n represents n
  • If 0 ≤ c ≤ 9 and ps represents p, then

carry(c, ps) represents c+p

  • If ps and qs represent p and q,

then add(ps, qs) represents p+q Induction on n Induction on length(ps) Induction on length(ps) * length(qs)

slide-59
SLIDE 59

yada yada yada

  • Similarly for times and mult
  • And for display…

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

slide-60
SLIDE 60

next time

  • An abstract type of dictionaries
  • Different ways to implement
  • association lists
  • binary search trees
  • red-black trees
  • Using functors to encapsulate constructions