CS52 - Spring 2016 - Class 5
Example code in this lecture
map_examples.sml
better_factorial.sml
lets_count.sml
subtraction.sml
Lecture notes
admin
- assignment 2 due Friday at 5pm
Why does recursion work? Why can we "trust" the recursive calls?
- the base case!
time saver: op
- look at the calcAreas example from last time (
map_examples.sml code
)
- What would be another name for the area function, i.e. what does it do?
- multiply
- 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;
bigger numbers in SML
- the largest integer we can represent in SML of type int is:
> Int.maxInt;
val it = SOME 1073741823 : int option
(* 1,073,741,823: a bit over a trillion *)
- what if we want numbers larger than that?
- reals partially solves the solution, although it also includes decimal values and it also has a maximum value
- a better solution is to make our representation for numbers that can get as large as we want
- ideas?
- represent numbers as a list of digits!
- lists can get as long as we'd like
- for logistic reasons, we're going to put the least significant digits at the *front* of the list and the most significant digits at the *back*
- for example:
- 5,432 in list form is [2, 3, 4, 5]
- 9,308,312 in list form is [2, 1, 3, 8, 0, 3, 9]
subtraction
5421
-780
----
...
- swb: subtract with borrow helper function
1. header
- first parameter is the borrow bit: 0 or 1 (we could do with a bool if you'd like as well)
- second parameter is number being subtracted *from*
- third parameter is the number being subtracted
val swb = fn: int -> int list -> int list -> int list
2. recursive case
- what are we recursing over?
- both of the lists!
swb b (l::ls) (r::rs) = ??? (swb ? ls rs)
- we want to make a recursive case on the rest of the list like we did by hand
- we may need to borrow
- how do we figure this out?
- digit = l - b - r
- if this is greater than or equal to 0, we're fine
- don't need to borrow
- digit is our answer
- if this value is less than 0
- we need to borrow
- digit + 10 is our answer
3. base cases
- we're recursing over two lists. What are the possible base cases?
- there are three!
- both are empty at the same time
- left is empty
- right is empty
- let's start with the cases where only one is empty
- right is empty
swb b l [] = ?
- we could try and handle this with an if statement depending on b
- better to just let it get handled by the recursive case!
swb b l [0]
- this is equivalent to adding a 0 to the front of the number
- left is empty
- we can do the same thing!
swb b [0] r
- both empty
- here it will depend if there is a borrow bit
- if there's no borrow, then we're done!
- if there is, then we've tried to borrow, but there's no numbers left
- the answer is negative!
- what does adding ~b do?
- adds a [~1] to the end of the list
- what effect does that have?
- more on this in a minute
4. put it all together
- swb is really just a helper function. We don't want to have to specify the borrow bit every time we try and subtract two lists
- look at the subDigitList function in
subtraction.sml code