CS52 - Fall 2015 - 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
       - mentor feedback

  • some debugging advice
       - test individual parts as much as possible
       
       - test recursive case manually
          - look at combList solution
          - consider combList 2 [1, 2, 3]
             - how could you check to see if you're recursive case is correct even without the base cases?
             - combList (m-1) xs = combList 1 [2,3] = [[2], [3]]
             - combList m xs = combList 2 [2,3] = [[2,3]]
             - (consAllk 1 [[2], [3]]) @ [[2,3]];
             val it = [[1,2],[1,3],[2,3]] : int list list
          - If this doesn't work right, then you know the recursive case isn't correct!
          - If it does work right, then you can be more confident that your recursive case is correct
             - just need to narrow down the problem with the base case(s)

       - What is the "_" variable in the solution?
          - It's just like putting x, y, a, b, etc.
          - But, it's a way of communicating to anyone reading the code that the variable doesn't matter!
             - notice that the variable isn't used anywhere on the right hand side
             - try and use this in your code

  • 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 NegativeNumber
           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 parameter
             - 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 function (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
                - 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 not, then we're done!
                   - if there is, then we've tried to borrow, but there's no numbers left
                      - the answer is negative!
                      - we'll talk about representing negative numbers next time
       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