Lecture 2
val succ = fn x => x+1;
val f = succ o (fn s => (size s)+3);
(* takes a string and returns its length + 4 *)
val day = fn 0 => "Monday"
| 1 => "Tuesday"
| 2 => "Wednesday"
| 3 => "Thursday"
| 4 => "Friday"
| 5 => "Saturday"
| _ => "Sunday";
(* convert a number into a day of the week *)
val day' = fn 0 => "Monday"
| 1 => "Tuesday"
| 2 => "Wednesday"
| 3 => "Thursday"
| 4 => "Friday"
| 5 => "Saturday"
| 6 => "Sunday";
(* same thing, but without a catch-all clause *)
val floor = Real.floor;
val real = Real.fromInt;
val zc =
fn (d,m,y,c) =>
(floor(2.61 * real m - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7;
val zeller = fn (d,1,y) => zc(d, 11, (y-1) mod 100, (y-1) div 100 + 1)
| (d,2,y) => zc(d, 12, (y-1) mod 100, (y-1) div 100 + 1)
| (d,m,y) => zc(d, m-2, y mod 100, y div 100 + 1);
(* day(zeller(d,m,y)) calculates the day of the week of the date d/m/y *)
local
val floor = Real.floor
val real = Real.fromInt
val zc =
fn (d,m,y,c) =>
(floor(2.61 * real m - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7
in
val zeller = fn (d,1,y) => zc(d, 11, (y-1) mod 100, (y-1) div 100 + 1)
| (d,2,y) => zc(d, 12, (y-1) mod 100, (y-1) div 100 + 1)
| (d,m,y) => zc(d, m-2, y mod 100, y div 100 + 1)
end;
(* same, but using local declarations *)
val sumformula = fn n => (n * (n+1)) div 2;
(* sum of the numbers from 1 to n *)
val rec sum = fn 1 => 1
| n => sum(n - 1) + n;
(* the same thing as a recursive function *)
val three = 3 and four = 4;
(* simultaneous bindings *)
val rec
f = fn x => g(x+1)
and
g = fn y => f(y-1);
(* a silly example of mutual recursion *)
Lecture 3
fun radix(n,base) =
let
val b = size base
fun digit m = str(String.sub(base,m))
fun radix'(true,m) = digit m
| radix'(false,m) = radix(m div b,base) ^ digit(m mod b)
in
radix'(n<b, n)
end
(* converts a non-negative integer into a string in a given base, represented
as a string of digits, e.g. decimal = "0123456789" *)
fun length nil = 0
| length(a::l) = 1 + length l;
(* length of a list *)
fun add nil = 0
| add(a::l) = a + add l;
(* sum of the numbers in a list *)
fun tail nil = nil
| tail(a::l) = l;
(* remove first element from a list *)
fun front nil = nil
| front(a::nil) = nil
| front(a::b::l) = a::(front(b::l));
(* remove last element from a list *)
fun zip nil l = nil
| zip l nil = nil
| zip (a::la) (b::lb) = (a,b)::(zip la lb);
(* turns a pair of lists into a list of pairs *)
String.explode "abc";
(* [ #"a", #"b", #"c" ] *)
String.implode [ #"a", #"b", #"c" ];
(* "abc" *)
Lecture 4
infixr 5 @;
fun nil @ l' = l'
| (a::l) @ l' = a :: (l @ l');
(* append two lists *)
fun rev nil = nil
| rev(a::l) = (rev l) @ [a];
(* reverse a list *)
local
fun revapp(nil,l') = l'
| revapp(a::l,l') = revapp(l,a::l')
in
fun rev' l = revapp(l,nil)
end;
(* a variant on rev *)
fun insert(x,nil) = [x]
| insert(x,h::t) = if x<=h then x::h::t
else h::insert(x,t);
(* insert elements into a sorted list
insert : int * int list -> int (the function <= is defined on integers,
reals, characters and strings, but "default overloading" picks int *)
fun sort nil = nil
| sort(h::t) = insert(h,sort t);
(* sort a list of integers *)
fun compose(f,g) = (fn x => f(g x));
(* function composition *)
fun compose(f,g) x = f(g x);
(* the same thing written slightly differently *)
fun twice f x = f(f x);
(* compose a function with itself *)
fun twice f = compose(f,f);
(* the same thing written differently *)
fun iter(f,0) x = x
| iter(f,n) x = f(iter (f,n-1) x);
(* n-fold composition of a function with itself *)
fun id x = x;
fun iter'(f,0) = id
| iter'(f,n) = compose(f, (iter'(f,n-1)));
(* the same thing written differently *)
Lecture 5
fun plus x y = x+y;
(* curried version of + *)
fun plus'(x,y) = x+y;
(* uncurried version of + *)
val succ = plus 1;
fun pow(x,0) = 1
| pow(x,n) = pow(x,n-1) * x;
(* x to the nth power *)
fun pow' x 0 = 1
| pow' x n = (pow' x (n-1)) * x;
(* curried version of pow *)
fun curry f x y = f(x,y);
(* turn an uncurried version of a function into a curried version *)
val plus = curry(op +);
(* same as plus above *)
fun uncurry f (x,y) = f x y;
+(* convert a curried function to an uncurried one *)
fun map f nil = nil
| map f (h::t) = (f h)::(map f t);
(* map : ('a -> 'b) -> 'a list -> 'b list
apply a function to every element of a list *)
fun reduce(g,f,m,n,e) =
if m>n then e
else g(reduce(g,f,m,n-1,e),f(n));
(* accumulate a result by applying a binary function g to a sequence of terms
f(i) for i from a starting value m to an final value n *)
fun sq n = reduce(op +, fn x => 2 * x - 1, 1, n, 0);
fun sum n = reduce(op +, id, 1, n, 0);
fun pow(x,n) = reduce(op *, fn _ => x, 1, n, 1);
fun fac n = reduce(op *, id, 1, n, 1);
fun exp(x,n) =
reduce(op +, fn y => real(pow(x,y))/real(fac y), 1, n, 1.0);
(* n-term approximation to e^x, using the formula
e^x = 1 + x/1 + x^2/1*2 + x^3/1*2*3 + ... *)
Lecture 6
fun id x = x;
(* id : 'a -> 'a *)
fun uncurry f (x,y) = f x y;
(* uncurry : ('a -> 'b -> 'c) -> ('a * 'b -> 'c) *)
Lecture 7
val me = { initial = #"D", surname = "Sannella", age = 44 };
(* me : { initial:char, surname:string, age:int } *)
#surname(me);
(* "Sannella" *)
type person = { initial:char, surname:string, age:int };
type 'a set = 'a -> bool;
type ('a,'b) gadget = ('a->'b) list -> ('b->'a) list;
datatype colour = red | blue | green;
(* introduces a new type and constructors red:colour, etc. *)
fun favourite blue = true
| favourite c = false;
datatype bool = true | false;
(* just like the built-in type *)
datatype inttree = empty | node of int * inttree * inttree;
(* binary trees of integers with constructors
empty : inttree
node : int * inttree * inttree -> inttree *)
datatype 'a tree = empty | node of 'a * 'a tree * 'a tree;
(* same thing, polymorphically *)
fun nodes empty = 0
| nodes(node(a,t,t')) = 1 + (nodes t) + (nodes t');
(* count number of nodes in a tree *)
datatype 'a list = nil | :: of 'a * 'a list;
infixr 5 :: ;
(* just like the built-in type *)
datatype exp = plus of term * term
| minus of term * term
| mkexp of term
and term = times of factor * factor
| divide of factor * factor
| mkterm of factor
and factor = const of int
| paren of exp;
(* mutually recursive datatype definitions *)
fun eval_exp(plus(t,t')) = (eval_term t) + (eval_term t')
| eval_exp(minus(t,t')) = (eval_term t) - (eval_term t')
| eval_exp(mkexp t) = (eval_term t)
and eval_term(times(f,f')) = (eval_factor f) * (eval_factor f')
| eval_term(divide(f,f')) = (eval_factor f) div (eval_factor f')
| eval_term(mkterm f) = (eval_factor f)
and eval_factor(const n) = n
| eval_factor(paren e) = eval_exp e;
(* mutually recursive functions for computing with mutually recursive
datatypes *)
datatype infintlist = cons of int * (unit->infintlist);
(* constructors can have functions as arguments *)
(* a version of Russell's paradox in ML *)
datatype set = mkset of set->bool;
(* a set is a collection of sets *)
fun russell(mkset f) = not(f(mkset f));
(* russell S is true iff S isn't a member of itself *)
(* Is mkset russell a member of mkset russell? By expanding the
definition of russell, we get
russell(mkset russell) = not(russell(mkset russell))
Why is this worrying? Why is it not so very worrying? *)
Lecture 8
val error = fn 1 => 1
| true => 1;
(* types of patterns don't match *)
val error = fn 1 => true
| n => "true";
(* types of results don't match *)
val warning = fn false => 3;
(* inexhaustive patterns *)
fun warning false = true
| warning true = false
| warning false = false;
(* redundant patterns *)
val error = fn (x,x) => true
| (x,y) => false;
(* repeated variables *)
fun error(~x) = x
| error 0 = 0
| error x = x;
(* ~ is a function, not a constructor *)
fun error(x + x) = true
| error(x + x + 1) = false;
(* + is a function; repeated variables *))
fun error(fn x => x) = true
| error f = false;
(* fn is not a constructor *)
let val Id = fn x => x in (Id 3, Id true) end;
(* this is well-typed *)
(fn Id =>(Id 3, Id true))(fn x => x);
(* this is not well-typed *)
Lecture 9
exception Empty;
fun tail nil = raise Empty
| tail(a::l) = l;
fun front nil = raise Empty
| front(a::nil) = nil
| front(a::b::l) = a::(front(b::l));
(* tail(nil) and front(nil) raise an exception *)
fun tail' nil = NONE
| tail'(a::l) = SOME l;
fun front' nil = NONE
| front'(a::nil) = SOME nil
| front'(a::b::l) =
let val SOME l' = front'(b::l) in SOME(a::l') end;
(* tail',front' : type 'a list -> 'a list option
tail'(nil) returns NONE, tail'(l) returns SOME(...) if l<>nil,
and similarly for front' *)
fun tester f x = SOME(f x) handle Empty => NONE;
(* converts from form with exceptions to form with 'a list option *)
fun tester f x = SOME(f x) handle _ => NONE;
(* a variant which deals with all exceptions instead of just Empty *)
exception Div0 of int;
fun mydiv(x,0) = raise Div0 x
| mydiv(x,y) = x div y;
(* Exceptions can carry data. To handle such an exception:
(...mydiv...) handle Div0 n => ...n... *)
Lecture 10
fun member(x,nil) = false
| member(x,h::t) = (x=h) orelse member(x,t);
(* member : ''a * ''a list -> bool, where ''a can be instantiated by
any type that admits equality *)
fun member'(x,p,nil) = false
| member'(x,p,h::t) = p(x,h) orelse member'(x,p,t);
(* member' : 'a * ('a * 'b -> bool) * 'b list -> bool
the equality function is passed as a parameter: instead of writing
member(a,l) we can write member'(a,op =,l) *)
fun exists(p,nil) = false
| exists(p,h::t) = p h orelse exists(p,t);
(* check to see if there is a value that satisfies a predicate
instead of writing member'(a,f,l) we can write exists(fn x=>f(a,x),l) *)
fun forall(p,nil) = true
| forall(p,h::t) = p h andalso forall(p,t);
fun foldr f e nil = e
| foldr f e (h::t) = f(h,foldr f e t);
(* foldr : ( 'a * 'b -> 'b ) -> 'b -> 'a list -> 'b
foldr f e [1,2,3] = f(1,f(2,f(3,e))) *)
fun sort' l = foldr insert nil l;
fun length' l = foldr(fn (x,y) => 1 + y) 0 l;
fun foldl f e nil = e
| foldl f e (h::t) = foldl f (f(h,e)) t;
(* foldl : ( 'a * 'b -> 'b ) -> 'b -> 'a list -> 'b
foldl f e [1,2,3] = f(3,f(2,(f(1,e)))) *)
fun rev' l = foldl (op ::) nil l;
type 'a delayed = unit -> 'a;
(* if exp:t then fn () => exp : t delayed *)
fun force d = d();
(* force : 'a delayed -> 'a; force(fn () => exp) = exp *)
Rest of SML core language
datatype 'a llist = lnil | lcons of 'a * ('a llist) delayed;
(* lazy lists *)
fun lones() = lcons(1,lones);
(* the infinite lazy list of ones *)
fun lfrom n = lcons(n,fn () => lfrom(n+1));
(* the infinite lazy list of integers starting from n *)
(* functions for accessing components of lazy lists *)
exception Lempty;
fun lhd lnil = raise Lempty
| lhd(lcons(n,l)) = n;
fun ltl lnil = raise Lempty
| ltl(lcons(_,l)) = force l;
fun lnth(0,l) = lhd l
| lnth(n,l) = lnth(n-1,ltl l);
fun lfromto(n,m) =
if n>m then lnil
else lcons(n,fn () => lfromto(n+1,m));
(* the lazy list of integers from n to m *)
fun lupto n = lfromto(2,n);
(* the lazy list of integers from 2 to m *)
fun divides(n,m) = ((m mod n) = 0);
(* check if n divides m *)
fun lforall(p,lnil) = true
| lforall(p,lcons(a,dll)) =
p(a) andalso lforall(p,force dll);
(* check if all the elements in a lazy list satisfy a predicate *)
fun lisprime n =
lforall((fn m => not(divides(m,n))), lupto(n-1));
(* check if n is prime by checking if it is divisible by any of
the integers from 2 to n-1 *)
fun lprune(p,lnil) = lnil
| lprune(p,lcons(a,dll)) =
if p(a)
then lcons(a,fn () => lprune(p,force dll))
else lprune(p,force dll);
(* remove all the elements in a lazy list that do not satisfy a predicate *)
val primes = lprune(lisprime,lfrom 2);
(* the lazy list of prime numbers *)
fun lprint(lnil) = ()
| lprint(lcons(a:int,dll)) =
(print(Int.toString a); print "\n"; lprint(force dll));
(* useful for printing lazy lists (if the list is infinite, you will
need to type ^C to stop) *)
val a = ref 1;
a := 3;
a := !a + 1;
(* an updatable variable is a reference to a value *)
datatype 'a dllist = endmarker
| cell of 'a * ('a dllist) ref * ('a dllist) ref;
(* doubly-linked lists *)
val x = 1;
fun f(y) = let val x=x+1 in x+y end;
(* f(3)=5 and f(3) again gives 5 *)
val x = ref 1;
fun f(y) = (x:= !x + 1; !x + y);
(* f(3)=5 and f(3) again gives 6 *)
fun ifac N =
let val n = ref N and i = ref 1
in
while !n <> 0 do
(i := !i * !n; n := !n - 1);
!i
end;
(* an iterative version of factorial *)
let val r = ref(fn x=>x)
in (r := (fn x => x+1);
!r true)
end;
(* will not typecheck, fortunately *)
SML module language
structure S =
struct
type t = int
val x = 3
fun f(z) = z * (z + 1)
end;
(* a structure binding; the structure expression is just an encapsulated
environment *)
structure T = S;
(* structure identifiers are structure expressions too *)
structure S =
struct
structure T =
struct
type t = int
val x = 3
end
datatype colour = red | blue
val y = 10
end;
(* T is a substructure of S *)
S.T.x + S.y : S.T.t;
(* qualified names for referring to components of structures *)
fun nice(S.red) = true
| nice(S.blue) = false;
(* ditto *)
local
open S
in
val a = T.x + y
end;
(* opening up a structure *)
signature SIG =
sig
type t
val x : int * t
end;
(* a signature binding *)
signature SIG' = SIG;
(* signature identifiers are signature expressions too *)
signature SIG' = SIG where type t=bool;
(* adding information about a type in an existing signature *)
signature SIG' =
sig
include SIG
val f : t -> t
end;
(* defining a signature by expanding another signature *)
signature SIG =
sig
type t
val x : t
val b : bool
end;
structure S : SIG =
struct
type t = int
val x = 3
val s = "abc"
val b = (x = size s)
end;
(* SIG doesn't mention s so S.s is hidden *)
signature SIG =
sig
type ty
type 'a tycon
val x : ty
val y : (int->ty) tycon
end;
structure S : SIG =
struct
type ty = bool
type 'a tycon = 'a * string
val x = true
val y = (fn x => x=3, "abc")
end;
(* S matches SIG because:
- S.ty is a type
- S.tycon is a unary type constructor
- S.x has type S.ty (= bool)
- S.y has type (int->ty) tycon (i.e. (int->bool) * string) *)
signature SIG =
sig
datatype 'a Dat = N | X of 'a * 'a | C of 'a * 'a Dat
end;
(* a structure matching SIG must define a unary type constructor Dat
with constructors N, X, C and no further constructors *)
signature SIG =
sig
type 'a Dat
val N : 'a Dat
val C : 'a * 'a Dat -> 'a Dat
end;
structure S : SIG =
struct
datatype 'a Dat = N | X of 'a * 'a | C of 'a * 'a Dat
end;
(* the signature constraint hides X, and it hides the fact that N and
C are constructors *)
signature SIGT =
sig
type t
val y : bool
end;
signature SIGS =
sig
structure T : SIGT
val x : T.t
end;
structure T : SIGT =
struct
type t = int
val y = true
end;
structure S : SIGS =
struct
structure T : SIGT = T
val x = 17
end;
(* in signatures specifying substructures, the definition of signature
matching is applied recursively *)
structure Colour =
struct
datatype colour = blend of int * int * int
val white = blend(0,0,0)
val red = blend(15,0,0)
val blue = blend(0,15,0)
val yellow = blend(0,0,15)
fun mix(parts:int, blend(r,b,y),
parts':int, blend(r',b',y')) =
blend(parts*r+parts'*r', parts*b+parts'*b', parts*y+parts'*y')
end;
(* colours are represented as triples of integers (amount of red,
amount of blue, amount of yellow); "blend" is a constructor *)
signature COLOUR =
sig
type colour
val white : colour
val red : colour
val blue : colour
val yellow : colour
val mix : int * colour * int * colour -> colour
end
structure Colour : COLOUR =
struct
datatype colour = blend of int * int * int
val white = blend(0,0,0)
val red = blend(15,0,0)
val blue = blend(0,15,0)
val yellow = blend(0,0,15)
fun mix(parts:int, blend(r,b,y),
parts':int, blend(r',b',y')) =
blend(parts*r+parts'*r', parts*b+parts'*b', parts*y+parts'*y')
end;
(* blend is hidden, but the colour will still admit equality *)
structure Colour' : COLOUR =
struct
type colour = int * int * int
val white = (0,0,0)
val red = (15,0,0)
val blue = (0,15,0)
val yellow = (0,0,15)
fun mix(parts:int, (r,b,y),
parts':int, (r',b',y')) =
(parts*r+parts'*r', parts*b+parts'*b', parts*y+parts'*y')
end;
(* a transparent signature constraint: the representation of colour is
revealed and colour admits equality *)
structure Colour'' :> COLOUR =
struct
type colour = int * int * int
val white = (0,0,0)
val red = (15,0,0)
val blue = (0,15,0)
val yellow = (0,0,15)
fun mix(parts:int, (r,b,y),
parts':int, (r',b',y')) =
(parts*r+parts'*r', parts*b+parts'*b', parts*y+parts'*y')
end;
(* an opaque signature constraint: the information that colour = int*int*int
is available inside the structure body but not outside, and colour admits
equality inside the structure body but not outside *)
signature PO =
sig
type t
val le : t * t -> bool
end;
functor ReverseOrder(P : PO) : PO =
struct
type t = P.t
fun le(x,y) = P.le(y,x)
end;
(* ReverseOrder takes any structure P matching PO and returns another
structure matching PO. *)
structure IntPO =
struct
type t = int
fun le(x:int,y:int) = x <= y
end;
structure IntPO' = ReverseOrder(IntPO);
structure IntPO'' = ReverseOrder(ReverseOrder(IntPO));
functor Id(P : SIG) : SIG = P;
(* Id is (almost) the identity function on structures matching SIG. *)
fun rev [] = []
| rev(x::xs) = (rev xs)@[x];
signature TRIVIAL =
sig
type t
end;
functor Rev(A:TRIVIAL) =
struct
fun rev [] = []:A.t list
| rev(x::xs) = (rev xs)@[x]
end;
(* Polymorphic functions are a lot like functors having TRIVIAL as
parameter signature *)
signature SORT =
sig
structure OBJ : PO
val sort : OBJ.t list -> OBJ.t list
end;
functor Sort(X : PO) : SORT =
struct
structure OBJ = X
fun partition(a,[]) = ([],[])
| partition(a,b::l) =
let
val (l1,l2) = partition(a,l)
in
if OBJ.le(b,a) then (b::l1,l2) else (l1,b::l2)
end
fun sort [] = []
| sort(a::l) = let
val (l1,l2) = partition(a,l)
in
(sort l1)@(a::(sort l2))
end
end;
(* This is a "quicksort" program. Partition is an auxiliary function. *)
structure S = Sort(IntPO);
S.sort [3,6,12,7,2,3,17,8];
functor Lexicographic(structure X : PO
structure Y : PO) : PO =
struct
type t = X.t * Y.t
fun le((x,y),(x',y')) =
if X.le(x,x')
then if X.le(x',x) then Y.le(y,y') else true
else false
end;
(* functors can have multiple parameters *)
structure A = Lexicographic(structure X = IntPO
structure Y = IntPO');
functor Rev(type t) =
struct
fun rev [] = []:t list
| rev(x::xs) = (rev xs)@[x]
end;
(* a functor parameter can be just a type or just a value *)
(* A SCHEMATIC EXAMPLE OF THE NEED FOR SHARING SPECIFICATIONS
structure Sym : SYM =
struct
type symbol = ...
...
end;
functor AbsSyn(Sym : SYM) : ABSSYN =
struct
structure S:SYM
type var = ...
val idname:var->S.symbol = ...
...
end;
functor SymTab(Sym : SYM) : SYMTAB =
struct
structure S:SYM
type table = ...
val lookup:S.symbol*table->bool = ...
...
end;
functor Parser(structure SymTab : SYMTAB
structure AbsSyn : ABSSYN) : PARSER =
struct
...
... if SymTab.lookup(AbsSyn.idname(v), t) then ...
...
end;
Won't typecheck: we don't know that AbsSyn.S.symbol=SymTab.S.symbol
The following is okay though:
functor Parser(structure SymTab : SYMTAB
structure AbsSyn : ABSSYN
sharing SymTab.S = AbsSyn.S) : PARSER =
struct
...
... if SymTab.lookup(AbsSyn.idname(v), t) then ...
...
end;
or
functor Parser(structure SymTab : SYMTAB
structure AbsSyn : ABSSYN
sharing type SymTab.S.symbol = AbsSyn.S.symbol) : PARSER =
struct
...
... if SymTab.lookup(AbsSyn.idname(v), t) then ...
...
end;
*)
functor Sort(X : PO) : SORT where type OBJ.t=X.t =
struct
structure OBJ = X
fun partition(a,[]) = ([],[])
| partition(a,b::l) =
let
val (l1,l2) = partition(a,l)
in
if OBJ.le(b,a) then (b::l1,l2) else (l1,b::l2)
end
fun sort [] = []
| sort(a::l) = let
val (l1,l2) = partition(a,l)
in
(sort l1)@(a::(sort l2))
end
end;
(* the result is required to be related to the parameter, and to
include sort : X.t list -> X.t list *)
functor Sort(X : PO) : SORT =
struct
structure OBJ = struct
type t = unit
fun le(x,y) = false
end
fun sort l = []
end;
(* the functor heading doesn't specify that the result has to have
anything to do with the parameter *)
Specifications in EML
val sqrt : real -> real = ?;
axiom forall a:real => sqrt(a) * sqrt(a) = a;
(* Let's assume that matrices, matrix multiplication and the identity matrix
have been previously defined. I will just give their types. We implement
matrix as (int list) list to sidestep an issue with equality that will be
explained later *)
type matrix = (int list) list;
val ** : matrix * matrix -> matrix = ?;
infix 7 **;
val I : matrix = ?;
val inv : matrix -> matrix = ?;
axiom inv(A) ** A = I;
axiom A ** inv(A) = I;
(* pre-conditions need to be added to the above specifications as follows *)
axiom a >= 0.0 implies sqrt(a) * sqrt(a) = a;
axiom exists B => (B ** A = I andalso A ** B = I)
implies
inv(A) ** A = I andalso A ** inv(A) = I;
(* nothing above here will work in EML because quantification must be explicit *)
axiom forall A => (exists B => B ** A = I andalso A ** B = I
implies
inv(A) ** A = I andalso A ** inv(A) = I);
fun member(a,[]) = false
| member(a,b::l) = (a=b) orelse member(a,l);
val maxelem : int list -> int = ?;
axiom forall l => l<>[] implies member(maxelem l, l);
axiom forall (a,l) => member(a,l) implies (maxelem l) >= a;
val before : string * string -> string = ?;
val after : string * string -> string = ?;
(* in the lecture, I referred to the library function String.isPrefix.
This isn't available in EML as currently defined, so let me just give its type *)
val isPrefix : string -> string -> bool = ?;
axiom forall (s,r) => before(s,r) ^ s ^ after(s,r) = r;
axiom forall (s,r) => forall (t1,t2) => t1^s^t2=r implies isPrefix (before(s,r)) t1;
(* before("i","specification") = "spec" and after("i","specification") = "fication" *)
val substring : string * string -> bool = ?;
axiom forall (s,r) => substring(s,r) implies before(s,r) ^ s ^ after(s,r) = r;
axiom forall (s,r) => substring(s,r) = (exists (t1,t2) => t1^s^t2=r);
axiom forall a => a >= 0.0 implies
sqrt(a) * sqrt(a) >= 0.99*a
andalso
sqrt(a) * sqrt(a) <= 1.01*a;
(* sqrt is allowed to produce any result within the specified interval *)
fun maxelem nil = 0
| maxelem([a]) = a
| maxelem(a::b::l) = if a>maxelem(b::l) then a
else maxelem(b::l);
(* We can use structural induction on lists to prove that this satisfies the
specification given above *)
structure Array =
struct
type array = int list
val empty = nil
fun put(n,v,nil) = if n=0 then v::nil else 0::put(n-1,v,nil)
| put(n,v,w::l) = if n=0 then v::l else w::put(n-1,v,l)
fun retrieve(n,nil) = 0
| retrieve(n,v::l) = if n=0 then v else retrieve(n-1,l)
end;
(* This implements an unbounded array of integers indexed starting from 0,
using a list of integers to represent the array. *)
forall (n,v,w,l) => Array.put(n,v,Array.put(n,w,l)) = Array.put(n,v,l)
forall (n,m,v,w,l) =>
n<>m implies Array.put(n,v,Array.put(m,w,l)) = Array.put(m,w,Array.put(n,v,l))
forall n => Array.retrieve(n,Array.empty) = 0
forall (n,v,l) => Array.retrieve(n,Array.put(n,v,l)) = v
forall (n,m,v,l) =>
n<>m implies Array.retrieve(n,Array.put(m,v,l)) = Array.retrieve(n,l)
(* These five axioms specify the functions Array.retrieve and Array.put and the
value Array.empty *)
signature ARRAY =
sig
type array
val empty : array
val put : int * int * array -> array
val retrieve : int * array -> int
axiom forall (n,v,w,l) => put(n,v,put(n,w,l)) = put(n,v,l)
axiom forall (n,m,v,w,l) =>
n<>m implies put(n,v,put(m,w,l)) = put(m,w,put(n,v,l))
axiom forall n => retrieve(n,empty) = 0
axiom forall (n,v,l) => retrieve(n,put(n,v,l)) = v
axiom forall (n,m,v,l) =>
n<>m implies retrieve(n,put(m,v,l)) = retrieve(n,l)
end;
(* This signature combines SML-style type information with axioms. But it is not
quite correct -- see below. *)
signature ARRAY =
sig
type array
val empty : array
val put : int * int * array -> array
val retrieve : int * array -> int
axiom forall (n,v,w,l) => put(n,v,put(n,w,l)) == put(n,v,l)
axiom forall (n,m,v,w,l) =>
n<>m implies put(n,v,put(m,w,l)) == put(m,w,put(n,v,l))
axiom forall n => retrieve(n,empty) == 0
axiom forall (n,v,l) => retrieve(n,put(n,v,l)) == v
axiom forall (n,m,v,l) =>
n<>m implies retrieve(n,put(m,v,l)) == retrieve(n,l)
end;
(* Since array does not admit equality, we need to use EML's "logical" equality ==
rather than SML's "computational" equality = . *)
structure Array :> ARRAY = ?;
(* Specifying a structure without providing an implementation *)
structure Array :> ARRAY =
struct
type array = int list
val empty = nil
fun put(n,v,nil) = if n=0 then v::nil else 0::put(n-1,v,nil)
| put(n,v,w::l) = if n=0 then v::l else w::put(n-1,v,l)
fun retrieve(n,nil) = 0
| retrieve(n,v::l) = ?
axiom forall (n,v,l) => retrieve(n,put(n,v,l)) == v
axiom forall (n,m,v,l) => n<>m implies retrieve(n,put(m,v,l)) == retrieve(n,l)
end;
(* Providing a partial implementation *)
(* Structure/functor bindings that use opaque signature constraints written with :>
will not be accepted by EML as currently defined because this was not in SML'90.
Changing :> to : will give the desired effect because all signature constraints
are opaque in EML. *)
signature HISTOGRAM =
sig
structure A : ARRAY
type histogram
val create : histogram
val incrementcount : int * histogram -> histogram
val display : histogram -> A.array
local
val count : int * histogram -> int
axiom forall n => count(n,create) == 0
axiom forall (n,h) => count(n,incrementcount(n,h)) == 1 + count(n,h)
axiom forall (n,m,h) => n<>m implies count(n,incrementcount(m,h)) == count(n,h)
in
axiom forall (n,h) => A.retrieve(n,display h) == count(n,h)
end
end;
structure Histogram :> HISTOGRAM = ?;
(* A specification of a structure containing functions for creating, updating and
displaying a histogram. *)
structure Histogram :> HISTOGRAM =
struct
structure A : ARRAY = Array
type histogram = A.array
val create = A.empty
fun incrementcount(n,h) = A.put(n, 1 + A.retrieve(n,h), h)
fun display h = h
end;
(* An implementation of the above specification *)
structure Histogram :> HISTOGRAM where type A.array = Array.array = ?;
(* We can require that the type of array produced by display is the same
type as used elsewhere in the system. *)
(* This will not be accepted by EML as currently defined both because of :> and
because of the "where ..." form. Here is how to get the same effect:
structure Histogram : sig include HISTOGRAM
sharing type A.array = Array.array
end = ?;
*)
(* To prove that Histogram satisfies HISTOGRAM we have to:
1. show that Array satisfies ARRAY
2. show that the code in Histogram satisfies the "visible" axiom in
HISTOGRAM, assuming that count is defined by the "hidden" axioms in
HISTOGRAM, and that A satisfies ARRAY. *)
signature PO =
sig
type t
val le : t * t -> bool
axiom forall x => le(x,x)
axiom forall (x,y,z) => le(x,y) andalso le(y,z) implies le(x,z)
axiom forall (x,y) => le(x,y) andalso le(y,x) implies x==y
axiom forall (x,y) => le(x,y) orelse le(y,x)
end;
fun count(a,[]) = 0
| count(a,b::l) = if a==b then 1+count(a,l) else count(a,l);
signature SORT =
sig
structure OBJ : PO
local
val permutation : 'a list * 'a list -> bool
axiom forall (l,l') =>
permutation(l,l') = (forall x => count(x,l)=count(x,l'))
val ordered : OBJ.t list -> bool
axiom forall l =>
ordered l =
(forall (a,b,c,x,y) =>
l==a@[x]@b@[y]@c implies OBJ.le(x,y))
in
val sort : OBJ.t list -> OBJ.t list
axiom forall l => permutation(l,sort l)
axiom forall l => ordered(sort l)
end
end;
functor Sort(X : PO) :> SORT where type OBJ.t=X.t = ?;
functor Sort(X : PO) :> SORT where type OBJ.t=X.t =
struct
structure OBJ = X
val partition : OBJ.t * OBJ.t list -> (OBJ.t list * OBJ.t list) = ?
axiom forall (a,l) =>
let val (l1,l2) = partition(a,l)
in forall b => member(b,l1) implies OBJ.le(b,a)
andalso
forall c => member(c,l2) implies OBJ.le(a,c)
andalso
forall d => member(d,l) iff member(d,l1) orelse member(d,l2)
end
fun sort [] = []
| sort(a::l) = let val (l1,l2) = partition(a,l)
in (sort l1)@(a::(sort l2))
end
end;
(* This supplies code for sort but only specifies partition. The axiom for
partition assumes that member has been defined earlier. *)
Don
Sannella. Please mail
me if you have any comments on this page.
Back to the
APS page.
Last modified: Tue Jun 29 12:34:16 BST 2004