More ML
Compiling Techniques
David Walker
Today
More data structures
lists
More functions
More modules
Next week: Lexical Analysis
element
Lists
Lists are created with
nil (makes empty list) head :: tail (makes a longer list)
5 :: nil : int list list of elements
element
Lists
Lists are created with
nil (makes empty list) head :: tail (makes a longer list)
4 :: (5 :: nil) : int list list of elements
element
Lists
Lists are created with
nil (makes empty list) head :: tail (makes a longer list)
3 :: 4 :: (5 :: nil) : int list list of elements
Lists
Lists are created with
3 :: (4 :: (5 :: nil)) : int list
3 :: 4 :: 5 :: nil : int list
(true, 1) :: (false, 2) :: nil : (bool * int) list
(3 :: nil) :: (2 :: nil) :: nil : (int list) list
Lists
Lists:
3 :: [] : int list
3 :: [4, 5] : int list
[true] : bool list a different way of writing “nil” a different way of writing a list
Lists
Bad List:
[4]::3;
???
Lists
Bad List:
[4]::3; stdIn:1.1-2.2 Error: operator and operand don't agree
[literal] operator domain: int list * int list list operand: int list * int in expression:
(4 :: nil) :: 3
Lists
Bad List:
[true, 5];
???
Lists
Bad List:
[true, 5]; stdIn:17.1-17.9 Error: operator and operand don't agree
[literal] operator domain: bool * bool list operand: bool * int list in expression: true :: 3 :: nil
List Processing
Functions over lists are usually defined by pattern matching on 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
Two arguments f and l fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l)
What does it do?
List Processing fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l) applies the function f to every element in the list
- fun add1 x = x + 1;
- map add1 [1,2,3];
> val it = [2,3,4] : int list
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) fn keyword introduces anonymous fun val isValue =
(fn t => case t of
Bool _ => true
| t => false) to the anonymous function value
Anonymous functions fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l) fun addlist x l = map ( fn y => y + x ) l anonymous functions
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) =>
(fn x => f (g x)) argument is pair of functions 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 isValue Num n = true
| isValue (Bool b) = true
| isValue (_) = false
This is just an abbreviation for fun isValue t = case t of
Num n => true
| Bool b => true
| _ => false
Yet another way to create a type error fun isValue 0 = true
| isValue (Bool b) = true
| isValue (_) = false 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: isValue =
(fn 0 => true
| Bool b => true
| _ => false)
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) a type variable stands for any type
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 : ??
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)
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)
What is the type of map?
fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l)
What is the type of map?
fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l)
Hint: top-level shape is:
.... -> ... -> ....
What is the type of map?
fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l)
Solution:
( ‘ a -> ‘ b) -> ‘ a list -> ‘ b list
ML Modules
Signatures
Interfaces
Structures
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 end exception Empty
...
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 type ‘a queue = ‘a list * ‘a list
...
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 signature QUEUE = sig type ‘a queue exception Empty abstract type
-- we don ’ t know the type ‘ a queue 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