Lecture 3: ML Functions and Polymorphism

advertisement
Lecture 3: ML Functions and Polymorphism
CS 6371: Advanced Programming Languages
September 5, 2012
- [1,2,3];;
val it = [1,2,3] : int list
- 1::2::3::[];;
val it = [1,2,3] : int list
- [1,2,”hello”];;
Error: operator and operand don’t agree
- fun add x y = x+y;;
val add = fn : int -> int -> int
- add 3 4;;
val it = 7 : int
- fun factorial 0 = 1
=
| factorial n = n*(factorial (n-1));;
val factorial = fn : int -> int
- fun sum [] = 0
| sum (x::t) = x+(sum t);;
val sum = fn : int list -> int
- sum [1,2,3];;
val it = 6 : int
- fun identity x = x;;
val identity = fn : ’a -> ’a
- identity 3;;
val it = 3 : int
- identity "foo";;
val it = "foo" : string
- fun apply f x = (f x);;
val apply = fn : ('a -> 'b) -> 'a -> 'b
- fun add (x,y) = x+y;;
val add = fn : int * int -> int
- apply add (1,2);;
val it = 3 : int
#apply add "foo";;
Error: operator and operand don’t agree
- fun map f [] = []
=
| map f (h::t) = (f h)::(map f t);;
val map = fn : ('a -> 'b) -> 'a list -> 'b
list
- fun addone n = n+1;;
val addone = fn : int -> int
- map addone [23,42,64];;
val it = [24,43,65] : int list
- map (fn n => n+1) [23;42;64];;
val it = [24,43,65] : int list
- (fn n => n+1);;
val it = fn : int -> int
- (fn n -> n+1) 2;;
val it = 3 : int
- fun compose f g = (fn x => f (g x));;
val compose = fn : ('a->'b) -> ('c->'a) ->
'c -> 'b
Lists are constructed using brackets or the cons
operator. Any given list has members of all the
same type.
Use “fun” to define a function. ML responds by
telling you the “type” of the new function you’ve
created. This one is a function from two integers
to an integer.
Instead of applying a function with syntax “f(x,y)”,
OCaml uses syntax “(f x y)”.
Functions are defined in cases. Each case is
pattern-matched in the order given.
The pattern [] matches the empty list, and the
pattern (x::y) matches lists with at least one
element (x) and whose tail (possibly empty) is y.
When possible, OCaml gives functions a
polymorphic type. Polymorphic functions can be
applied to arguments of any type.
However, there must be some consistent way to
instantiate each type variable. Here we see an
example where no such instantiation exists and
the compiler therefore rejects the code.
List arguments can also have polymorphic type.
Use “fn” to create anonymous (i.e., unnamed)
functions. “fn … => …” is the same as if you typed
“fun foo … = …;;” and then used “foo”.
Using anonymous functions, you can build and
return functions as values at runtime.
- val cool = (compose (fn n -> n+1)
=
(fn n -> n*2));;
val cool = fn : int -> int
- cool 1;;
val it = 3 : int
- fun addx x = (fn y => x+y);;
val addx = fn : int -> int -> int
- addx 1;;
val it = fn : int -> int
- (addx 1) 2;;
val it = 3 : int
- addx 1 2;;
val it = 3 : int
- val add = (fn x => (fn y => x+y));;
add : int -> int -> int
- add 1 2;;
val it = 3 : int
- add 1;;
val it = fn : int -> int
- fun add x y = x+y;;
val add = fn : int -> int -> int
- map (add 3) [1,2,3];;
val it = [4,5,6] : int list
- fun insert cmp x [] = [x]
=
| insert cmp x (h::t) =
=
if (cmp x h) then (x::h::t)
=
else (h::(insert cmp x t));;
val insert = fn : (’a -> ’a -> bool) -> ’a
-> ’a list -> ’a list
- fun isort cmp [] = []
=
| isort cmp (h::t) =
=
insert cmp h (isort cmp t);;
val isort = fn : (’a -> ’a -> bool) -> ’a
list -> ’a list
- isort (fn x=>fn y=> x<y) [15,2,27,16];;
val it = [2,15,16,27] : int list
- isort (fn x=>fn y=> x>y) [15,2,27,16];;
val it = [27,16,15,2] : int list
- fun count 0 = []
=
| count n = n::(count (n-1));;
val count = fn : int -> int list
- count 5;;
val it = [5,4,3,2,1] : int list
- fun app [] y = y
=
| app (h::t) y = h::(app t y);;
val app = fn:’a list -> ’a list -> ’a list
- fun rev [] = []
=
| rev (h::t) = app (rev t) [h];;
val rev = fn : ’a list -> ’a list
- fun count a 0 = a
=
| count a n = count (n::a) (n-1);;
val count = fn:int list -> int -> int list
- count [] 5;;
val it = [1,2,3,4,5] : int list
An anonymous function may refer to variables
declared in outer scopes.
Actually “fun foo x y = …” is just an abbreviation
for “val foo = (fn x => (fn y => …))”. If you give such
a function fewer arguments than it expects, it
yields a function from the remaining arguments to
the original value. Functions written this way are
called “curried functions”. Applying fewer
arguments is called “partial evaluation”.
Let’s use the above to implement a polymorphic
insertion sort function.
Immutable variables do not hinder the power of
the language. Here is an example of counting with
immutable variables.
To count in the other direction, we can use listreverse. To get list-reverse, we first need an
implementation of list-append.
The above implementations are not tail-recursive
because they generate O(n)-height call stacks.
Here’s a tail-recursive implementation of count
whose maximum stack height is O(1). Dr. Gupta
will cover tail-recursion next time.
Download