CS54 - Fall 2022 - Class 3
Example code in this lecture
interval.sml
list_basics.sml
rev_examples.sml
Lecture notes
admin
- assignment 0
- how did it go?
- if you still don't have SML installed, reach out to Corey
- make sure you're using consistent and informative formatting
- assignment 1
- due Sunday
- start now!
- can (and are encouraged to) work in pairs
- keep up with the readings
- mentor and office hours posted
- CS slack channel: https://tinyurl.com/PomonaCSSlack
Academic honesty and collaboration
- Syllabus
- Understand what is appropriate and what is not!
- I want you to be able to talk with other students and support each other. I also want the *assignment* work submitted to be your own.
- Some things to watch out for:
- Don't procrastinate!
- One of the common ways I've seen people get into trouble is by not leaving enough time to work on the assignment.
- If you find yourself in this situation, just turn in what you have and take it from there.
- Do not give your work to anyone!
- It doesn't help them.
- It doesn't help you (and can get you into trouble).
- Understand what type of collaboration is ok and what type is not.
- You MAY:
- Use/copy ANY of the examples covered in class or in the readings (in fact, you're encouraged to!)
- Look at and work through examples from class with a partner or group
- Talk about issues raised in the reading with a partner or group
- Talk at a high-level (i.e. NO CODING) about the assignments with a partner or group
- Help someone with a syntax problem (i.e. code won't compile)... this is the only time you should look at someone else's code
- Help someone understand an error message
- You may NOT:
- look at anyone else's code to help yourself
- give or show your code to anyone else (except to help diagnose a syntax problem)
- search online for answers, solutions, etc.
- sit next to someone while working that you are talking with about the assignment (ideally, just don't sit next to anyone who's in the class)
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
Comments, an aside
- SML has one type of comment, similar to Java's /* */ comment
- Comment start with (* and end with *)
- 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 :)
- (x+k)::(addTo k xs)
- 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 = addTo 47;
val add47To = fn : int list -> int list
> add47To [10];
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 code
- 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
- return the answer
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];
look at rev in
rev_examples.sml code
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...