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...