CS54 - Fall 2022 - Class 6
Example code in this lecture
map_mystery.sml
better_factorial.sml
lets_count.sml
Lecture notes
admin
- assignment 2: how did it go?
- assignment 3 out later today
map
- the mystery function is so important (and common) that it is built into sml as a function call map
- very powerful function
- if you feel like you want to "loop" over a list, map can often be used instead of writing an additional recursive function.
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 mult (w, h) = w*h;
- Now, Let's put it together using map
> fun calc_areas lst = map mult 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 following declare?
> val calc2 = map mult;
- Map is curried and we can just instantiate the first argument
- Two ways to think about this:
1) the type signature hints at what the function does:
('a -> 'b) -> 'a list -> 'b list
is instantiated with the mult type signature (int * int) -> int resulting in:
(int * int) list -> int list
2) alternatively, if we look at the mystery function in
map_mystery.sml code
and think about hard-coding f to "mult", we see that we get a function that takes a list of tuples, applies mult and concatenates the answer
- This is just another way of defining calc_areas!
time saver: op
- In the example above, we defined a new function mult. It seems a waste to define a new function where there is already a multiply operator (*)
- can we just write?
fun calcAreas lst = map * lst;
- No!
- this just tries to multiply map by lst
- * is an infix operator that expects its arguments to be on either side
- op helps us in this situation
- op takes an operator as input and returns a prefix function (i.e. normal) where the input parameters are pass as a tuple:
x ? y -> ? (x, y)
- For example:
> op*;
val it = fn : int * int -> int
- Does this help us with calcAreas?
> fun calcAreas lst = map op* lst;
val calcAreas = fn : (int * int) list -> int list
- Note this works for all the operators (we'll see more use for this later)
> op/;
val it = fn : real * real -> real
> op div;
val it = fn : int * int -> int
> op::;
val it = fn : 'a * 'a list -> 'a list
> op@;
val it = fn : 'a list * 'a list -> 'a list
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)
a better factorial
- factorial is a function that should only be called on non-negative numbers
- look at the badFactorial function in
better_factorial.sml code
- hopefully your code looked something like this
- here I've had it return 0 for any negative number
- this avoids the infinite recursion
- but still isn't very satisfying
- what's a better approach?
- throw/raise an exception!
exceptions in SML
- You can define a new exception in SML by adding:
exception <name_of_exception>;
- By convention, the name of the exception will be capitalized
- You can then "raise" an exception anywhere in the code by adding:
raise <name_of_exception>
- look at the factorial function in
better_factorial.sml code
- we declared an exception called NegativeNumber
- in the case where someone passes a number less than 0 we raise the exception
> factorial 4;
val it = 24 : int
> factorial ~4;
uncaught exception NegativeNumberException
raised at: better_factorial.sml:13.15-13.29
- I'll talk in a couple of weeks about handling exceptions (aka catching exceptions)
let (count example)
- write a function called "count":
- takes two parameters
- a value
- a list of values
- returns a tuple containing how many times that value occurs in the list AND the list with all occurrences of the item removed as the second element
- We could fairly easily write this as three functions (one helper function for each value and a third to put it all together)
- I want to write it as a single recursive function.
1. function header
val count = fn: 'a -> 'a list -> (int * 'a list)
2. recursive case
- will we be recursing on the list or the number?
- list
- assume the recursive call works, what would it give us back?
- count a (x::xs) = ??? (count a xs)
- (count a xs)
- how many times a occurs in xs
- xs with all occurrence of a removed
- as a tuple
- if we had this information, what would we need to do?
- just check a against x
- if it's equal
- add 1 to the number of occurrences
- don't include x
- it it's not equal
- keep the number of occurrences the same
- include x
- how can we write this?
if a = x then
(count a xs) ???
- since count returns a tuple, we need a way to get at the values of the tuple
- two ways
- less elegant way: #1 (don't do this!)
- there are functions #1, #2, ... that get at elements in a tuple
> #1 (10, 20)
val it = 10 : int
> #2 (10, 20);
val it = 20 : int
- a better way
- we can use a let statement to unpack a tuple (or many other types of values!)
- look at the count function in
lets_count.sml code
- we declare a new value in the let statement that is a tuple and call the recursive case
- num will get the first value of the tuple and lst the second
- we have unpacked the tuple
- we can then use the values in the if statements below
- this technique works on any pattern type things, e.g.
let
val (x::xs) = some_call_that_returns_a_nonempty_list
in
(* use x and xs *)
end;