You are on page 1of 47

# 15-150 Fall 2014

Stephen Brookes

announcements
Midterm exams: pick up in lab A 87.5
B
80.0
Average score on exam: 72.0
C 69.5
D 60.0
Median score: 73.0
Midterm grade based on HW + MIDTERM
Labs will count toward final grade

last time
!

Modular programming
Signatures and structures
Information hiding

## abstract data types

The ML module system facilitates

## Users are given a limited repertoire of

operations for building values

## These operations are designed to work

correctly and efficiently, assuming some
suitable representation invariant

## Users cannot ever break the rules!

our example
The signature for ARITH specifies an
abstract data type

A type integer
Equipped with basic operations

initialize
rep
:
int
->
integer

## add, mult : integer * integer -> integer

combine
display : integer -> string
examine

fun rep 0 = [ ] !
| rep n = (n mod 10) :: rep(n div 10)!

## rep : int -> int list

REQUIRES n 0
ENSURES (rep n) = a list L (of decimal digits)
representing n
eval L = n
fun eval [ ] = 0 !
| eval (d::ds) = d + 10 * eval ds!

(abbreviation)
fun rep 0 = [ ] !
| rep n = (n mod 10) :: rep(n div 10)!

## rep : int -> int list

REQUIRES n 0
ENSURES (rep n) represents n

## fun carry (0, ps) = ps!

| carry(c, [ ]) = [c]!
| carry (c, p::ps) = !
((p+c) mod 10) :: carry ((p+c) div 10, ps)
carry : int * int list -> int list
REQUIRES 0 c 9, L represents n
ENSURES carry(c, L) represents c+n

## fun add ([ ], qs) = qs!

| add (ps, [ ]) = ps!
| add (p::ps, q::qs) =!
((p+q) mod 10) :: carry ((p+q) div 10, add(ps, qs))
add : int list * int list -> int list
REQUIRES L represents x, R represents y
ENSURES add(L, R) represents x+y

## fun times (0, qs) = [ ]!

| times (p, [ ]) = [ ] !
| times (p, q::qs) =!
((p * q) mod 10) :: carry ((p * q) div 10, times(p, qs))
times : int * int list -> int list
REQUIRES 0 c 9, L represents n
ENSURES times(c, L) represents c*n

fun mult ([ ], _) = [ ]
| mult(_, [ ]) = [ ]
| mult (p::ps, qs) = add (times(p, qs), 0 :: mult (ps, qs))

!

## mult : int list * int list -> int list

REQUIRES L represents x, R represents y
ENSURES mult(L, R) represents x*y

correctness
Dec implements non-negative integers

## Every non-negative value is representable

add implements +

mult implements *
How can we establish this?

invariant
To prove correctness we introduce a

representation invariant
inv(L) :: L is a list of decimal digits
This is a property guaranteed to hold,
for all values of type integer constructible from
rep(n) with n0, using add, mult

## and must ensure that it holds of their results

abstraction function
And we (re-)introduce an abstraction function
eval : integer -> int
to explain how

integer values represent int values
eval L = the int represented by L
(only needs to make sense on values that satisfy inv)

inv
(* inv : int list -> bool *)!
fun inv [ ] = true!
| inv (d::L) = (0 <= d andalso d <= 9) andalso inv L

eval
(* eval : int list -> int *)!
fun eval [ ] = 0!
| eval (d::L) = d + 10 * eval(L)

termination
For all L:int list,
For all L:int list,
For all n0,

inv(L) terminates

eval(L) terminates

rep(n) terminates

## When ps and qs satisfy inv,

add(ps, qs) terminates

& mult(ps, qs) terminates

invariance

Proofs?

## Every integer value built from

rep(n) with n0, add, and mult
satisfies inv

## For all n0, rep(n) satisfies inv

For all ps, qs : int list,
if ps and qs satisfy inv
so do add(ps, qs) and mult(ps, qs)

invariance
!

## if ps and qs satisfy inv

so does add(ps, qs)

LEMMA

## If ps satisfies inv and 0 c 9,

then carry(c, ps) satisfies inv

correctness
For all ps, qs : int list,

then

## eval(add(ps, qs)) = (eval ps) + (eval qs)

eval(mult(ps, qs)) = (eval ps) * (eval qs)

correctness
If ps and qs satisfy inv
then
eval(add(ps, qs)) = (eval ps) + (eval qs)
LEMMA

If ps satisfies inv

and 0 c 9,

## then carry(c, ps) = a list L such that

eval L = c + eval ps

## fun carry (0, ps) = ps!

| carry(c, [ ]) = [c]!
| carry (c, p::ps) = !
((p+c) mod 10) :: carry ((p+c) div 10, ps)
LEMMA

If ps satisfies inv

and 0 c 9,

## then carry(c, ps) = a list L such that

eval L = c + eval ps
PROOF

By induction on ps

an alternative
structure Dec2 : ARITH =!
struct!
type digit = int !
type integer = digit list!

What changes?

Anything significant?

## fun rep 0 =  !

| rep n = (n mod 10) :: rep(n div 10)!
!

end

## Dec and Dec2

The values used to represent 0 are different

Dec.rep 0 = [ ]
Dec2.rep 0 = 
But users ascribing opaquely to ARITH
cannot distinguish between Dec and Dec2
Dec.display (Dec.rep 0) = "0"
Dec2.display (Dec2.rep 0) = "0"

## These two implementations

are indistinguishable

(representational invariance)

questions
Why decimal?

Could have used binary

## Lets consider binary...

binary digits
structure Bin : ARITH =!
struct!
type digit = int !
type integer = digit list!

just replace 10 by 2

in the code for Dec

fun rep 0 = [ ] !
| rep n = (n mod 2) :: rep(n div 2)!
!

...

end

correctness
Bin implements non-negative integers

## Every non-negative value is representable

add implements +

mult implements *
add(rep 1, rep 1) = [0,1]

invariant
To prove correctness we introduce a
representation invariant
inv2 : int list -> bool

inv2(L) = true

iff

every item in L is a binary digit
01

abstraction
And we define an abstraction function
eval2 : integer -> int
For all L : int list such that inv2(L) = true,

eval2 L = the int value represented
in binary by L

correctness

## For all n0, rep(n) satisfies inv

and eval2(rep n) = n

## If L and R satisfy inv2

so does add(L, R), and
eval2(add(L, R))

= (eval2 L) + (eval2 R)
(similarly for mult)

meta-proof
A correctness proof for Dec,

with 10 replaced by 2,
yields a correctness proof for Bin

## deja deja deja vu

This all looks very similar

## To get octal representation, replace 10 by 8

To get ternary representation, replace 10 by 3

## Lets encapsulate the common design...

What we need is a parameterized
structure definition...

## ... with a parameter that specifies a base

functors
An ML functor is a

## Like a function from structures to structures

Its argument and result have signatures
rather than types

## functor <name> (<arg> : <sig>) : <sig> =

struct

end

BASE
signature BASE =!
sig!
val base : int!
end

Digits
functor Digits(B : BASE) : ARITH =!
struct!
val b = B.base!
type digit = int (* use 0 through b-1 *)!
type integer = digit list!
!
fun rep 0 = [ ]!
| rep n = (n mod b) :: rep(n div b)!
!

!

end

## functor Digits(B : BASE) : ARITH =

struct

val b = B.base

type digit = int (* uses 0 through b-1*)

type integer = digit list

fun rep 0 = [ ] | rep n = (n mod b) :: rep (n div b)

## (* carry : digit * integer -> integer *)

fun carry (0, ps) = ps

| carry (c, [ ]) = [c]

| carry (c, p::ps) = ((p+c) mod b) :: carry ((p+c) div b, ps)

fun add ([ ], qs) = qs

| add (ps, [ ]) = ps

| add (p::ps, q::qs) =

((p+q) mod b) :: carry ((p+q) div b, add(ps,qs))

(* times : digit -> integer -> integer *)

fun times 0 qs = [ ]

| times k [ ] = [ ]

| times k (q::qs) =

((k * q) mod b) :: carry ((k * q) div b, times k qs)

fun mult ([ ], _) = [ ]

| mult (_, [ ]) = [ ]

| mult (p::ps, qs) = add (times p qs, 0 :: mult (ps,qs))

fun display L = foldl (fn (d, s) => Int.toString d ^ s) "" L

end

using Digits
functor applied to argument

!

an

anonymous

structure

## Dec.rep 42 = [2,4] : Dec.integer

Bin.rep 42 = [0,1,0,1,0,1] : Bin.integer
fun decfact(n:int) : Dec.integer =!
if n=0 then Dec.rep 1 else Dec.mult(Dec.rep n, decfact(n-1));!
!

## fun binfact(n:int) : Bin.integer =!

if n=0 then Bin.rep 1 else Bin.mult(Bin.rep n, binfact(n-1));

whats visible?
functor Digits(B : BASE) : ARITH =!
struct!
val b = B.base;!
type digit = int (* use 0 through b-1 *)!
type integer = digit list!
...

signature ARITH =!
sig!
type integer!
...!
end;

## The type Dec.integer is int list

The type Bin.integer is int list
- Bin.rep 42;!
val it = [0,1,0,1,0,1] : Bin.integer!
!

- it : int list!
val it = [0,1,0,1,0,1] : int list

oops!
- Bin.add(Dec.rep 42, Dec.rep 42);!
!

## val it = [0,0,1,2] : Bin.integer

solution (1)
In the functor body, make integer a

## Then adapt the code for rep, etc...

functor Digits(B : BASE) : ARITH =!
struct!
val b = B.base;!
type digit = int (* use 0 through b-1 *)!
datatype digits = D of digit list!
type integer = digits!
!

fun rep 0 = D [ ] !
| rep n = let val (D L) = rep(n div b) in D((n mod b)::L) end!
...

## functor Digits(B:BASE) : ARITH =

struct

val b = B.base

type digit = int (* uses 0 through b-1 *)

datatype digits = D of digit list

type integer = digits

solution (1)

!
!

fun rep 0 = D [ ]

| rep n = let val (D L) = rep(n div b) in D ((n mod b) :: L) end

(* carry : digit * digit list -> digit list *)

fun carry (0, ps) = ps

| carry (c, [ ]) = [c]

| carry (c, p::ps) = ((p+c) mod b) :: carry ((p+c) div b, ps)

!
!

## (* adder : digit list * digit list -> digit list *)

fun adder ([ ], qs) = qs

| adder (ps, [ ]) = ps

| adder (p::ps, q::qs) =

((p+q) mod b) :: carry ((p+q) div b, adder(ps,qs))

(* add : integer * integer -> integer *)

fun add (D L, D R) = D (adder(L, R))

(* times : digit -> digit list -> digit list *)

fun times 0 qs = [ ]

| times k [ ] = [ ]

| times k (q::qs) =

((k * q) mod b) :: carry ((k * q) div b, times k qs)

(* multer : digit list * digit list -> digit list *)

fun multer ([ ], _) = [ ]

| multer (_, [ ]) = [ ]

| multer (p::ps, qs) = adder (times p qs, 0 :: multer (ps,qs))

(* mult : integer * integer -> integer *)

fun mult(D L, D R) = D(multer(L,R))

end

## structure Dec = Digits(struct val base = 10 end);!

!
structure Bin = Digits(struct val base = 2 end);

- Dec.rep 42;

val it = D [2,4] : Dec.integer

!

- Bin.rep 42;

val it = D [0,1,0,1,0,1] : Bin.integer

!

- D [1+1] = D ;

Error: unbound variable or constructor: D

## - Bin.add(Dec.rep 42, Dec.rep 42);!

Error:operator and operand don't agree
[tycon mismatch]

solution (2)
Leave the functor body as is,

## but ascribe the signature opaquely

functor Digits(B : BASE) :> ARITH =!
struct!
val b = B.base;!
type digit = int (* use 0 through b-1 *)!
type integer = digit list!
!

fun rep 0 = [ ] !
| rep n = (n mod b) :: rep (n div b)!
...

problem solved
With either of these solutions,
the code fragment

## Bin.add(Dec.rep 42, Dec.rep 42)

is not well-typed,

so cannot be evaluated.

transparency
- Bin.rep 42;!
val it = [0,1,0,1,0,1] : Bin.integer!
!
- Dec.rep 42;!
val it = [2,4] : Dec.integer

opacity
- Bin.rep 42;!
val it = - : Bin.integer!
!
- Dec.rep 42;!
val it = - : Dec.integer

## Dec.integer = int list

Bin.integer = int list

testing
With opaque ascription
- Dec.rep 42;!
val it = - : Dec.integer

- it = [2,4];!

type error

## Convert it to a visible type!

- Dec.display(Dec.rep 42);!
val it = "42" : string

correctness

what happens if

B.base = 1?

## Define invb and evalb as before

For all n0, S.rep(n) satisfies invb

If v1, v2 : S.integer satisfy invb so does S.add(v1, v2)
and evalb(S.add(v1, v2)) = (evalb v1) + (evalb v2)

unary
structure Unary : ARITH = Digits(struct val b = 1 end);

open Unary;

display(add(rep 3, rep 2));

## What goes wrong? Why?

Didnt we prove correctness?

fun rep 0 = [ ] !
| rep n = (n mod 1) :: rep(n div 1)!

eval1(rep n) n
Figure out where the
correctness proof breaks!

## abstract data types

The ML module systems facilitates program
design based on abstract data types

## Users can be given a limited repertoire of

operations for building values

## These operations can be designed to work

correctly and efficiently, assuming some
suitable representation invariant