### CS52 - Spring 2017 - Class 3

#### Example code in this lecture

interval.sml
list_basics.sml
rev_examples.sml

#### Lecture notes

- assignment 0
- make sure you're using consistent and informative formatting
- assignment 1
- due Friday at 5pm
- start now!
- keep up with the readings
- mentor hours
- lunch with CS faculty members
- Terminal tutorial session tonight!

• look at the interval function in interval.sml code
- What does it do?
- takes two numbers as parameters
- how do you know they're numbers?
- m+1
- n <= m
- creates a list (using ::)
- the list contains the number from m to n
- including m and n?
- including m
- not including n

> interval 1 10;
val it = [1,2,3,4,5,6,7,8,9] : int list
- What is its type signature (curried or uncurried)?
val interval = fn : int -> int -> int list

- curried function
- has two parameters (sort of), both ints
- gives us back a list of ints!

• look at the interval2 function in interval.sml code
- does the same thing!
- uses @ instead of ::
- notice that because we're using @, we have to do [n-1] because @ expects two lists
- functionally these behave the same, but there are some differences in performance (more on this later!)
> interval 1 100000;
val it = [1,2,3,4,5,6,7,8,9,10,11,12,...] : int list
> interval2 1 100000;
Interrupt

- SML has one type of comment, similar to Java's /* */ comment
- They can span multiple lines
- Commenting code:
- You should have a comment at the top of any file you create explaining what's in there, your name, etc.
- All functions should have comments above them explaining what that function does
- If there are any complicated portions of the functions (or funny parts) you should also put a small comment to the side of that line
- In general, SML tends to have less comments than other languages because the code is more compact and self-contained. BUT, you still should be putting in *some* comments

• look at the addTo function in list_basics.sml code
- We can also do recursion on lists
- this will be a very, very common thing we'll see in this class
- follows very naturally from the recursive definition of lists. a list is:
- an element
- and the rest of the list
- recursion is then:
- do something with the element
- recursively process the rest of the list

- What does it do and what is the type signature?
- val addTo = fn: int -> int list -> int list
- adds k to all elements in the list
- Has two patterns corresponding to the base case and the recursive case
- recursive case
- we can pattern match a non-empty list using (x::xs)
- x gets the first element in the list
- xs gets the rest of the list (xs = excess :)
- the value is a list with x+k at the front
- where the rest of the list is xs with k added to each element
- base case
- eventually, as we process the elements of the first list, we will get to the end, which will be an empty list
- this will pattern match to the first case
- what is the empty list with k added to it?
- just []

- Curried or uncurried?
- curried
- by making it curried we can make our own specialized functions:
val add47To = fn : int list -> int list
val it = [57] : int list
> add47To [1, 2, 3, 4];
val it = [48,49,51,51] : int list

- Notice again that there is a parameter, k, that's just along for the ride
- the recursion is on the second parameter, the list

• look at the deleteLast function in list_basics.sml
- what does the second pattern match, i.e. deleteLast [x]?
- any list with a single item in it
- x then will contain that single value
- what is the type signature of this function?
- take as input a list and returns a list
- what type of list?
- any type!
- the only constraint is that the input and output lists are of the same type

deleteLast: 'a list -> 'a list

- SML indicates this using type variables ('a)
- what does this function do?
- deletes the last element of the list
- the third pattern simply copies the list
- eventually, we get to the point where the list is a single item
- in that case, we "delete" the item by not copying it (i.e. returning the empty list)
- We include the [] base case for completeness (otherwise, SML would yell at us)
- probably better to raise an exception in this case (but we haven't talked about that yet!)

• writing recursive functions
1. define what the function header is (i.e. name and type signature)
- what parameters does the function take? curried or uncurried?
- what value does the function return
2. define the recursive case
- pretend you had a working version of your function, but it only works on smaller versions of your current problem, how could you write your function?
- the recursive problem should be getting "smaller", by some definition of smaller
- other ideas:
- sometimes define it in English first and then translate that into code
- often nice to think about it mathematically, using equals
3. define the base case
- recursive calls should be making the problem "smaller"
- what is the smallest (or simplest) problem?
4. put it all together
- first, check the base case
- return something (or do something) for the base case
- if the base case isn't true
- calculate the problem using the recursive definition

• An example: reverse
- Write a function called rev that takes a list and gives us a reversed version of that list

1. val rev = fn: 'a list -> 'a list
- takes a list of values and returns the same values in a list, but reversed

2. rev (x::xs) = ?
- what would we get back if we called rev on xs?
- all of xs reversed, as a list
- trust the recursion
- how could we then use that answer to get our overall answer?
- put x at the end of it
= (rev xs) @ [x]
- why can't we just put (rev xs) @ x?
- @ expects two lists as arguments!

3. Each recursive call makes the list smaller. Eventually, it will be very, very easy to reverse
- rev [] = []

4. Put it all together:
fun rev nil = nil
| rev (x::xs) = (rev xs) @ [x];

• Efficiency
- I claim that this is not a very efficient implementation of reverse
- How could we check this?
- make a big list and reverse it!
- Let's use our interval function we defined before. What does it do?
- creates a list of numbers, m ... n-1
- We can run rev on increasingly larger lists:
> interval 1 10;
val it = [1,2,3,4,5,6,7,8,9] : int list
> rev (interval 1 10);
val it = [9,8,7,6,5,4,3,2,1] : int list
> rev (interval 1 100);
val it = [99,98,97,96,95,94,93,92,91,90,89,88,...] : int list

An aside, SML won't display the full list, though this can be updated

> rev (interval 1 1000);
val it = [999,998,997,996,995,994,993,992,991,990,989,988,...] : int list
> rev (interval 1 10000);
val it = [9999,9998,9997,9996,9995,9994,9993,9992,9991,9990,9989,9988,...] : int list
> rev (interval 1 100000);

It works ok for small lists, but for longer lists it takes a while...

• A more efficient reverse
- The problem turns out to be (we'll talk about this more later) that @ is a linear time algorithm (see assignment 1 for implementation details :)
- Let's try and solve the problem without using @
- Tell me what the following function does:

fun revAux (acc, []) = acc
| revAux (acc, x::xs) = revAux(x::acc, xs);

- First, what is it's type signature?
val revAux = fn: 'a list * 'a list -> 'a list
rev_examples.sml rev_examples.sml
- Is this a curried or uncurried function?
- uncurried

- What does it actually do?
- adds the elements of second argument/list in reverse order to the first argument

- How is revAux useful for rev?
- if we call it with an empty list as the first argument, then it's reverse!
- We could just write:
fun rev2 lst = revAux ([],lst)

- Any problems with this?
- Any time you have an auxiliary or helper function, better to encapsulate it for use only with this function

• information hiding with rev_examples.sml
- let allows you to define values (e.g. functions) that are only available inside a certain block of code
- syntax

let
<val declaration1>; (including function definitions)
<val declaration2>;
...
in
<expression>
end;

- the values declared inside of let ... in are only available inside the block of code in ... end
- the value of the entire let expression is the value of <expression>

• back to reverse
- Often it's a good idea to write and test your auxiliary function separately and THEN put it in the let statement
- A better version of rev: look at rev2 in rev_examples.sml code

- Now, let's see if it's any more efficient!
> rev2 [1, 2, 3, 4];
val it = [4,3,2,1] : int list
> rev2 (interval 1 100000);
val it = [99999,99998,99997,99996,99995,99994,99993,99992,99991,99990,99989,99988, ...] : int list