View
226
Download
0
Embed Size (px)
Citation preview
Introduction to ML Last time:
Basics: integers, Booleans, tuples,... simple functions introduction to data types
This time, we continue writing an evaluator for a simple language
Pierce, Chapter 4 (in O’Caml) Show how to use:
more functions exceptions modules
A little language (LL) An arithmetic expression e is
a boolean value an if statement (if e1 then e2 else e3) the number zero the successor of a number the predecessor of a number a test for zero (isZero e)
LL abstract syntax in ML
datatype term = Bool of bool| If of term * term * term| Zero| Successor of term | Predecessor of term| IsZero of term
datatype declarationsgive you three things:
1. a new type
2. constructors functions for building objects of the new type
3. patterns for decomposingdatatypes
LL abstract syntax in ML
If (Bool true, Zero, Successor (Successor Zero))
represents “if true then 0 else succ(succ 0)”
If
Booltrue ZeroSuc.
Suc.Zero
Function declarations
fun isNumberValue t = case t of Zero => true | Successor t2 => isNumberValue t2 | _ => false
function name function parameter
default pattern matches anything
A very subtle error
fun isNumberValue t = case t of zero => true | Successor t2 => isNumberValue t2 | _ => false
The code above type checks. But whenwe test it refined the function always returns “true.”What has gone wrong?
A very subtle error
fun isNumberValue t = case t of zero => true | Successor t2 => isNumberValue t2 | _ => false
The code above type checks. But whenwe test it refined the function always returns “true.”What has gone wrong?-- zero is not capitalized-- ML treats it like a variable pattern (matches anything!)
Another function
fun isNumberValue t = ...
fun isValue t = case t of Bool _ => true | t => isNumberValue t
Exceptions
exception Error of string
fun debug s : unit = raise (Error s)
Exceptions
exception Error of string
fun debug s : unit = raise (Error s)
- debug "hello";
uncaught exception Error raised at: ex.sml:15.28-15.35
in SML interpreter:
Evaluator
fun isNumberValue t = ...fun isValue t = ...
exception NoRule
fun eval1 t = case t of Bool _ | Zero => raise NoRule...
Evaluator
...
fun eval1 t = case t of Bool _ | Zero => raise NoRule | If(Bool b,t2,t3) => (if b then t2 else t3) | If(t1,t2,t3) => If (eval1 t1,t2,t3) ...
Evaluatorexception NoRule
fun eval1 t = case t of ... | Successor t => if isValue t then raise NoRule else let val t’ = eval1 t in Successor t’ end
let expression:
let declarationsin expressionend
Finishing the Evaluatorfun eval1 t = case t of ... | ... | Successor t => ... | Predecessor t => ... | IsZero t => ...
be sure yourcase isexhaustive
Finishing the Evaluatorfun eval1 t = case t of ... | ... | Successor t => ... What if we
forgot a case?
Finishing the Evaluator
ex.sml:25.2-35.12 Warning: match nonexhaustive (Bool _ | Zero) => ... If (Bool b,t2,t3) => ... If (t1,t2,t3) => ... Successor t => ...
fun eval1 t = case t of ... | ... | Successor t => ... What if we
forgot a case?
Multi-step evaluation
fun eval1 t = ...
fun eval t = let fun loop t = loop (eval1 t) val message = “Done\n” in ((loop t) handle NoRule => print message | Error s => print s) end
Be verycarefulwith thesyntax ofhandle(use extra parens)
Done with the evaluator
ML is all about functions There are many different ways to
define functions! I almost always use “fun f x = ...” When I am only going to use a
function once and it is not recursive, I write an anonymous function: (fn x => ...)
Anonymous functions
val n = 3
val isValue = (fn t => case t of Bool _ => true | t => isNumberValue t)
binds a variable (n)to a value (3)
binds a variable(isValue)
to the anonymous function valuefn keyword
introducesanonymousfun
Anonymous functions
type ifun = int -> int
val intCompose : ifun * ifun -> ifun = ...
fun add3 x = intCompose ((fn x => x + 2), (fn y => y + 1)) x
a pair of anonymous functions
a type definition (very convenient)
Anonymous functions
type ifun = int -> int
val intCompose : ifun * ifun -> ifun = fn (f,g) => (fn x => f (g x))
fun add3 x = intCompose ((fn x => x + 2), (fn y => y + 1)) x
result is a function!
argument is pair of functionspatternmatchagainstarg
Another way to write a function
fun f x = ........
can be written as:
val f = (fn x => ......)
provided the function is not recursive;f does not appear in ........
Another way to write a function
fun f x = ....
can always be written as:
val rec f = (fn x => ...f can be used here...)
keyword rec declares a recursive function value
Yet another way to write a function
fun isNumberValue Zero = true | isNumberValue (Successor t2) = true | isNumberValue (_) = true
This is just an abbreviation for
fun isNumberValue t = case t of Zero => true | Successor t2 => true | _ => true
Yet another way to create a type errorfun isNumberValue 0 = true | isNumberValue (Successor t2) = true | isNumberValue (_) = true
ex.sml:9.1-11.29 Error: parameter or result constraints of clauses don't agree [literal] this clause: term -> 'Z previous clauses: int -> 'Z in declaration: isNumberValue = (fn 0 => true | Successor t2 => true | _ => true)
Parametric Polymorphism Functions like compose work on objects
of many different types
val compose = fn f => fn g => fn x => f (g x)
compose (fn x => x + 1) (fn x => x + 2)
compose not (fn x => x < 17)
Parametric Polymorphism Functions like compose work on objects
of many different types
val compose = fn f => fn g => fn x => f (g x)
compose not (fn x => x < 17)
compose (fn x => x < 17) not BAD!!
Parametric Polymorphism Functions like compose work on objects
of many different types
val compose = fn f => fn g => fn x => f (g x)
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
Note: type variables are written with ‘
a type variablestands forany type
Parametric Polymorphism Functions like compose work on objects
of many different types
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
compose: (int -> ‘b) -> (‘c -> int) -> (‘c -> ‘b)
can be used as if it had the type:
Parametric Polymorphism Functions like compose work on objects
of many different types
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
compose: ((int * int) -> ‘b) -> (‘c -> (int * int)) -> (‘c -> ‘b)
can be used as if it had the type:
Parametric Polymorphism Functions like compose work on objects
of many different types
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
compose: (unit -> int) -> (int -> unit) -> (int -> int)
can be used as if it had the type:
Parametric Polymorphism
compose not : ??
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)not : bool -> bool
Parametric Polymorphism
compose not : ??
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)not : bool -> bool
type of compose’s argument must equalthe type of not:bool -> bool == (‘a -> ‘b)
Parametric Polymorphism
compose not : ??
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)not : bool -> bool
‘a must be bool‘b must be bool as well(in this use of compose)
therefore:
Parametric Polymorphism
compose not : (‘c -> bool) -> (c’ -> bool)
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)not : bool -> bool
‘a = bool‘b = bool
Parametric Polymorphism
compose not : (‘c -> bool) -> (c’ -> bool)
(compose not) not : bool -> bool
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)not : bool -> bool
Parametric Polymorphism
compose not : (‘c -> bool) -> (c’ -> bool)
(compose not) not : ??
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)not : bool -> bool
Parametric Polymorphism
compose (fn x => x) : ?
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
Parametric Polymorphism
compose (fn x => x) : ?
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
‘d -> ‘d
Parametric Polymorphism
compose (fn x => x) : ?
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
‘d -> ‘d
must be the same
ie:
‘a = ‘d‘b = ‘d
Parametric Polymorphism
compose (fn x => x) : ?
compose : (‘d -> ‘d) -> (‘c -> ‘d) -> (‘c -> ‘d)
‘d -> ‘d
must be the same
ie:
‘a = ‘d‘b = ‘d
Parametric Polymorphism
compose (fn x => x) : ?
compose : (‘d -> ‘d) -> (‘c -> ‘d) -> (‘c -> ‘d)
‘d -> ‘d
(‘c -> ‘d) -> (‘c -> ‘d)
Lists Lists:
nil : ‘a list
:: : ‘a * ‘a list -> ‘a list
3 :: 4 :: 5 :: nil : int list
(fn x => x) :: nil : (‘a -> ‘a) list
Lists Lists:
[] : ‘a list
3 :: [4, 5] : int list
[fn x => x] : (‘a -> ‘a) list
a different wayof writing “nil”
List Processing
Functions over lists are usually defined by case analysis (induction) over the structure of a list
Hint: often, the structure of a function isguided by the type of the argument (recall eval)
fun length l = case l of nil => 0 l _ :: l => 1 + (length l)
List Processing
fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l)
an incredibly useful function:
- map (fn x => x+1) [1,2,3]; > val it = [2,3,4] ; int list
List Processing
fun fold f a l = case l of nil => a l x :: l => f (fold f a l) x
another incredibly useful function
what does it do? what is its type?use it to write map.
ML Modules Signatures
Interfaces Structures
Implementations Functors
Parameterized structures Functions from structures to
structures
Structures
structure Queue = struct
type ‘a queue = ‘a list * ‘a listexception Emptyval empty = (nil, nil)fun insert (x, q) = …fun remove q = …
end
Structures
structure Queue = struct
type ‘a queue = ‘a list * ‘a listexception Empty
...end
fun insert2 q x y = Queue.insert (y, Queue.insert (q, x))
Structures
structure Queue = struct
...end
structure Q = Queue
fun insert2 q x y = Q.insert (y, Q.insert (q, x))
convenientabbreviation
Structures
structure Queue = struct
...end
open Queue
fun insert2 q x y = insert (y, insert (q, x))
for lazy programmers
-- not encouraged!
Structures
structure Queue = struct
type ‘a queue = ‘a list * ‘a list...
end
fun insert2 (q1,q2) x y : ‘a queue = (x::y::q1,q2)
by default,all componentsof the structure may be used
-- we know the type ‘a queue
Signatures
signature QUEUE =sig
type ‘a queueexception Emptyval empty : ‘a queueval insert : ‘a * ‘a queue -> ‘a queueval remove : ‘a queue -> ‘a * ‘a
queue end
abstract type
-- we don’t know the type ‘a queue
Information hiding
signature QUEUE = sig type ‘a queue ... end
structure Queue :> QUEUE = struct type ‘a queue = ‘a list * ‘a list val empty = (nil, nil) … end
fun insert2 (q1,q2) x y : ‘a queue = (x::y::q1,q2)
does nottype check
Signature Ascription Opaque ascription
Provides abstract typesstructure Queue :> QUEUE = …
Transparent ascription A special case of opaque ascription Hides fields but does not make types
abstractstructure Queue_E : QUEUE = …
SEE Harper, chapters 18-22 for more on modules
Other things functors (functions from structures
to structures) references (mutable data structures)
ref e; !e; e1 := e2 while loops, for loops arrays (* comments (* can be *) nested *) a bunch of other stuff...
Last Things Learning to program in SML can be
tricky at first But once you get used to it, you
will never want to go back to imperative languages
Check out the reference materials listed on the course homepage