intro2

advertisement
Introduction to ML

Last time:




This time, we continue writing an evaluator for a
simple language


Basics: integers, Booleans, tuples,...
simple functions
introduction to data types
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 declarations
give you three things:
1. a new type
2. constructors functions
for building objects of
the new type
3. patterns for decomposing
datatypes
LL abstract syntax in ML
If (Bool true, Zero, Successor (Successor Zero))
represents “if true then 0 else succ(succ 0)”
If
Booltrue
Suc.
Zero
Suc.
Zero
Function declarations
function name
function parameter
fun isNumberValue t =
case t of
Zero => true
| Successor t2 => isNumberValue t2
| _ => false
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 when
we 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 when
we 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)
in SML interpreter:
- debug "hello";
uncaught exception Error
raised at: ex.sml:15.28-15.35
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)
...
Evaluator
exception 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
declarations
in
expression
end
Finishing the Evaluator
fun eval1 t =
case t of
...
| ...
| Successor t => ...
| Predecessor t => ...
| IsZero t => ...
be sure your
case is
exhaustive
Finishing the Evaluator
fun eval1 t =
case t of
...
| ...
| Successor t => ...
What if we
forgot a case?
Finishing the Evaluator
fun eval1 t =
case t of
...
| ...
| Successor t => ...
What if we
forgot a case?
ex.sml:25.2-35.12 Warning: match nonexhaustive
(Bool _ | Zero) => ...
If (Bool b,t2,t3) => ...
If (t1,t2,t3) => ...
Successor t => ...
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 very
careful
with the
syntax of
handle
(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
binds a variable (n)
to a value (3)
binds a variable
(isValue)
val isValue =
(fn t =>
case t of
Bool _ => true
fn keyword
| t => isNumberValue t)
introduces
anonymous
fun
to the anonymous
function value
Anonymous functions
a type definition (very convenient)
type ifun = int -> int
val intCompose : ifun * ifun -> ifun = ...
a pair of anonymous functions
fun add3 x =
intCompose ((fn x => x + 2), (fn y => y + 1)) x
Anonymous functions
type ifun = int -> int
pattern
match
against
arg
val intCompose : ifun * ifun -> ifun =
fn (f,g) =>
argument is pair of functions
(fn x => f (g x))
result is a function!
fun add3 x =
intCompose ((fn x => x + 2), (fn y => y + 1)) x
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 error
fun 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)
a type variable
stands for
any type
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
Note: type variables are written with ‘
Parametric Polymorphism

Functions like compose work on objects of
many different types
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
can be used as if it had the type:
compose: (int -> ‘b) -> (‘c -> int) -> (‘c -> ‘b)
Parametric Polymorphism

Functions like compose work on objects of
many different types
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
can be used as if it had the type:
compose: ((int * int) -> ‘b) -> (‘c -> (int * int)) -> (‘c -> ‘b)
Parametric Polymorphism

Functions like compose work on objects of
many different types
compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
can be used as if it had the type:
compose: (unit -> int) -> (int -> unit) -> (int -> int)
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
not : bool -> bool

compose not : ??
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
not : bool -> bool

compose not : ??
type of compose’s argument must equal
the type of not:
bool -> bool == (‘a -> ‘b)
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
not : bool -> bool

compose not : ??
therefore: ‘a must be bool
‘b must be bool as well
(in this use of compose)
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
not : bool -> bool

‘a = bool
‘b = bool
compose not : (‘c -> bool) -> (c’ -> bool)
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
not : bool -> bool


compose not : (‘c -> bool) -> (c’ -> bool)
(compose not) not : bool -> bool
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)
not : bool -> bool


compose not : (‘c -> bool) -> (c’ -> bool)
(compose not) not : ??
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)

compose (fn x => x) : ?
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)

compose (fn x => x) : ?
‘d -> ‘d
Parametric Polymorphism
compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)

compose (fn x => x) : ?
‘d -> ‘d
must be
the same
ie:
‘a = ‘d
‘b = ‘d
Parametric Polymorphism
compose : (‘d -> ‘d) -> (‘c -> ‘d) -> (‘c -> ‘d)

compose (fn x => x) : ?
‘d -> ‘d
must be
the same
ie:
‘a = ‘d
‘b = ‘d
Parametric Polymorphism
compose : (‘d -> ‘d) -> (‘c -> ‘d) -> (‘c -> ‘d)

compose (fn x => x) : ?
‘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
a different way
of writing “nil”

3 :: [4, 5] : int list

[fn x => x] : (‘a -> ‘a) list
List Processing

Functions over lists are usually defined by case
analysis (induction) over the structure of a list
fun length l =
case l of
nil => 0
l _ :: l => 1 + (length l)
Hint: often, the structure of a function is
guided by the type of the argument (recall eval)

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


Structures


Interfaces
Implementations
Functors


Parameterized structures
Functions from structures to structures
Structures

structure Queue =
struct
type ‘a queue = ‘a list * ‘a list
exception Empty
val empty = (nil, nil)
fun insert (x, q) = …
fun remove q = …
end
Structures
structure Queue =
struct
type ‘a queue = ‘a list * ‘a list
exception 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))
convenient
abbreviation
Structures
structure Queue =
struct
...
end
for lazy
programmers
-- not encouraged!
open Queue
fun insert2 q x y =
insert (y, insert (q, x))
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 components
of the structure
may be used
-- we know the
type ‘a queue
Signatures
abstract type
signature QUEUE =
-- we don’t know the
sig
type ‘a queue
type ‘a queue
exception Empty
val empty : ‘a queue
val insert : ‘a * ‘a queue -> ‘a queue
val remove : ‘a queue -> ‘a * ‘a queue
end
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
does not
type check
fun insert2 (q1,q2) x y : ‘a queue =
(x::y::q1,q2)
Signature Ascription

Opaque ascription
Provides abstract types
structure Queue :> QUEUE = …


Transparent ascription
A special case of opaque ascription
 Hides fields but does not make types abstract
structure 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
Download