CS52 - Spring 2017 - Class 5
Example code in this lecture
uniquify.sml
map_mystery.sml
better_factorial.sml
lets_count.sml
Lecture notes
admin
- assignment 2 due Friday at 5pm
- Updated check file
- A few clarifications:
- semi-colons are optional for let expression declarations
- = and <> are NOT defined for reals
- Assignment submission
- 5pm is the deadline. No late submissions.
- Do not list collaborators. This messes up the grading scripts.
- Make sure to put your name in the comments at the top of the file
- Make sure to include comments above each function.
- Run the assignment checker! It checks two things:
1) that the function exists and
2) that it has the correct type signature
look at the member function in
uniquify.sml code
- What is its type signature?
''a -> ''a list -> bool
- An aside, some times you will see sml use double quotes instead of single quotes for a type variable (i.e. ''a vs. 'a). There is a slight distinction, though we won't emphasize it in this class:
- single quoted variable (e.g. 'a) indicates any general type
- double quoted variable (e.g. ''a) indicates any type that supports equality
- in the member function we are making an equality comparison, which is why it uses a double quoted type variable
- What does member do?
- checks if the first argument occurs in the list
- in the recursive call we check if "e=x". If this is true, then "true orelse <anything>" will be true
- note SML will do short-circuited computation and will actually stop the recursion once it finds the element, though this doesn't affect the answer
- only if we check all of the elements and we get to the base case do we then return false
look at the uniquify function in
uniquify.sml code
- What is its type signature?
''a list -> ''a list
- What does uniquify do?
- Takes in a list and returns the same list with all the duplicates removed
- For each element it uses member to ask if that element is in the rest of the list
- if it is, then it does not include that element in the answer and just recurses on xs
- if it is not, then it does get included along with the recursion on the rest of the list
- uniquify will keep the *last* occurrence of a repeated element because it will continue to skip it until it doesn't find it in the rest of the list
- In particular
uniquify [1, 2, 3, 1, 4, 2, 3]
yields [1, 4, 2, 3] corresponding to the last occurrences of these
- if we had kept the first occurrences it would have been [1, 2, 3, 4] (you'll have to code this up for the assignment :)
look at the mystery function in
map_mystery.sml code
- What is its type signature?
('a -> 'b) -> 'a list -> 'b list
- Note that the first argument is a function because we're using it in the context of a function, i.e. (f x), a function call
- What does it do?
- For each element in the list, calls the function with that element and adds the result of that function call to the result.
- For example:
> fun negate x = ~x;
val negate = fn : int -> int
> negate 7;
val it = ~7 : int
> mystery negate [1, 2, 3, 4];
val it = [~1,~2,~3,~4] : int list
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
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 as the first element 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 on 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;
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)