CS52 - Spring 2016 - Class 4
Example code in this lecture
list_of_lists.sml
map_examples.sml
Lecture notes
admin
- assignment 1 due Friday at 5pm
- assignment 2 out tomorrow
- you can start already!
- academic honesty
- be mindful!
- class participation
SML debugging/programming
- SML requires thinking NOT hacking
- Work through the recursion by hand and/or small problems by hand (e.g. for comb1 and combList)
- Check your recursion by calculating by hand the recursive case and then trying out the recusion, e.g:
- rev [1, 2, 3, 4] - you guess that the recursive case is:
- rev (x::xs) = (rev xs)::[x]
- You can work though it by hand: rev [1, 2, 3, 4]
- x = 1
- xs = [2, 3, 4]
- assuming rev works correctly (rev xs) => [4, 3, 2]
- we can now type the recusrive case:
- [4, 3, 2] :: [1]
stdIn:7.1-7.17 Error: operator and operand don't agree [literal]
operator domain: int list * int list list
operand: int list * int list
in expression:
(4 :: 3 :: 2 :: nil) :: 1 :: nil
What does the following function do?
fun mystery [] = false
| mystery [x] = true
| mystery (x::y::xs) = mystery xs;
- What is the type signature of this function?
val mystery = fn: 'a list -> bool
- What are the three patterns matching?
- []: empty list
- [x]: a list with one element
- (x::y::xs): a list with at least two elements in it
- x gets the first element
- y get the second element
- What does it do?
- returns true if the list has an odd length, false if it has an even length
match nonexhaustive
- What if we'd mistakenly typed:
fun mystery [] = false
| mystery (x::y::xs) = mystery xs;
i.e. left off the second pattern?
- It will compile, but SML will issue a warning!
mystery2.sml:1.6-2.36 Warning: match nonexhaustive
nil => ...
x :: y :: xs => ...
val mystery = fn : 'a list -> bool
- What's the issue?
- We cannot call mystery with every type of list
- In particular, if we were to call it with an odd length list, we'd get to a point where there wasn't a pattern to match!
- Be careful! Sometimes these can be subtle. Anything wrong the following function?
fun ex 0 [] = 0
| ex y (x::xs) = (ex (y+x) xs);
- Still match nonexhaustive
- missing the case where the first parameter is non-zero and the second parameter is the empty list
- matches require *both* things to match (i.e. like "and")
warnings in SML
- In general, most warnings you CANNOT ignore
- Almost all of them point at a problem with your code
- there are two exceptions, i.e. warnings you *can* ignore:
> [] = [];
stdIn:13.4 Warning: calling polyEqual
val it = true : bool
> [] @ [];
stdIn:11.1-11.8 Warning: type vars not generalized because of
value restriction are instantiated to dummy types (X1,X2,...)
val it = [] : ?.X1 list
write a function appendAll that takes a list of lists as a parameter and appends them all into a single list
- type signature?
- val appendAll = fn: 'a list list -> 'a list
- look at appendAll function in
list_of_lists.sml code
- why @ and not ::?
- x is a list that we want to add to, not treat x as an individual element
infiltrate
- Let's write a function called infiltrate:
- it takes two parameters, a number and a list and inserts that number into all possible positions in the list, e.g.
> infiltrate 0 [1, 2, 3];
val it = [[0,1,2,3],[1,0,2,3],[1,2,0,3],[1,2,3,0]] : int list list
> infiltrate "a" ["b", "c", "d"];
val it = [["a","b","c","d"],["b","a","c","d"],["b","c","a","d"],["b","c","d","a"]] : string list list
- Walk through the four recursion steps:
1. function header/type signature
val infiltrate = fn: 'a -> 'a list -> 'a list list
2. recursive case
- infiltrate x (y::ys) => ?
- What would we get if we called (infiltrate x ys)?
- A list of lists with x inserted into all possible positions of ys
- e.g. infiltrate 0 [2, 3]
- [[0, 2, 3], [2, 0, 3], [2, 3, 0]]
- Given this answer, how could we get our answer to the bigger problem (e.g. infiltrate 0 [1, 2, 3])?
- We're missing the 1 from all of the examples:
[[0, 2, 3], [2, 0, 3], [2, 3, 0]] =>
[[1, 0, 2, 3], [1, 2, 0, 3], [1, 2, 3, 0]]
- are we done?
- missing [0, 1, 2, 3]
- How can we do this?
(x::y::ys) :: (consAllk y (infiltrate x ys))
3. base case
- how is the problem getting smaller?
- the list is getting smaller
- when this is the case, often base case of []
- What should infiltrate x [] give us?
- [[x]]
- why not [x]?
- we're supposed to be returning a list of lists
4. put it all together!
- look at the infiltrate function in
list_of_lists.sml code
map
- SML has a built-in function called map, here is the type signature:
fn : ('a -> 'b) -> 'a list -> 'b list
- curried or uncurried? How many parameters?
- curried
- two parameters
- What are the parameters?
- ('a -> 'b)
- some function!
- 'a list
- a list of things
- Any guesses to what map does?
- map applies the function to each of the elements in the list
- For example:
> fun negate x = ~x;
val negate = fn : int -> int
> negate 7;
val it = ~7 : int
> map negate [1, 2, 3, 4];
val it = [~1,,~2,~3,~4] : int list
Another map example
- I want to write a function called calc_areas that takes a list of (base, height) tuples representing the sides of rectangles and then calculates the area of these rectangles
- What will be the type signature?
val calc_areas = fn: (int * int) list -> int list
- Let's do this *using map*?
- First, let's write a function that just calculates the area given a tuple, calculate the area:
fun area (w, h) = w*h;
- Now, Let's put it together using map
> fun calc_areas lst = map area lst;
val calc_areas = fn : (int * int) list -> int list
> calc_areas [(1, 2), (4, 5), (6, 2)];
val it = [2,20,12] : int list
One last example:
- What does the mystery function below do (and what is its type signature)?
fun add a b = a + b;
fun mystery lst = map (add 47) lst;
- First, what does (add 47) give us?
- it gives us a function that takes a single argument (and int) and add 47 to it
- for example,
> val add47 = add 47;
val add47 = fn: int -> int
- We could have also written this:
> val mystery = map (add 47);
val mystery = fn: int list -> int list
- Why?
- map is a curried function
- we've supplied the first argument therefore we're left with a function requiring a second argument, a list
- how does SML know it's an int list?
- (add 47) is a function int -> int
see all these map examples in
map_examples.sml code
Our own map
- What does this function do:
fun mystery f [] = []
| mystery f (x::xs) = (f x) :: (mystery f xs);
- It's the map function!
- Notice a few things:
- even though map is very powerful, it's very easy to write in SML
- functions are first order values so we can make them parameters (f above) and use them
anonymous functions
- sometimes we only need a function for one simple use case, e.g. when we were trying to add 47 to all the elements in a list
- we could write the function as:
fun add47 x = x+47;
- and then use it in map like we did
- we can also just create a function on the fly, what is called an anonymous function (because it doesn't have a name!)
- the syntax is:
fn <parameters> => <body>
- for example, we could write the add47 function as:
fn x => x+47;
- We can embed this in a statement, for example:
fun add47TOAll lst = map (fn x => x+47) lst
- or even more interesting
fun addToAll k lst = map (fn x => x+k) lst
- or if you want to get fancy
val addToAll k = map (fn x => x+k)