CS52 - Spring 2017 - Class 5

Example code in this lecture

   uniquify.sml
   map_mystery.sml
   better_factorial.sml
   lets_count.sml

Lecture notes

  • admin
       - assignment 2 due Friday at 5pm
          - Updated check file
       - A few clarifications:   
          - semi-colons are optional for let expression declarations
          - = and <> are NOT defined for reals
       - Assignment submission
          - 5pm is the deadline. No late submissions.
          - Do not list collaborators. This messes up the grading scripts.
          - Make sure to put your name in the comments at the top of the file
          - Make sure to include comments above each function.
          - Run the assignment checker! It checks two things:
             1) that the function exists and
             2) that it has the correct type signature

  • look at the member function in uniquify.sml code
       - What is its type signature?
          ''a -> ''a list -> bool

       - An aside, some times you will see sml use double quotes instead of single quotes for a type variable (i.e. ''a vs. 'a). There is a slight distinction, though we won't emphasize it in this class:
          - single quoted variable (e.g. 'a) indicates any general type
          - double quoted variable (e.g. ''a) indicates any type that supports equality
             - in the member function we are making an equality comparison, which is why it uses a double quoted type variable   

       - What does member do?
          - checks if the first argument occurs in the list
          - in the recursive call we check if "e=x". If this is true, then "true orelse <anything>" will be true
             - note SML will do short-circuited computation and will actually stop the recursion once it finds the element, though this doesn't affect the answer
          - only if we check all of the elements and we get to the base case do we then return false

  • look at the uniquify function in uniquify.sml code
       - What is its type signature?
          ''a list -> ''a list

       - What does uniquify do?
          - Takes in a list and returns the same list with all the duplicates removed
          - For each element it uses member to ask if that element is in the rest of the list
             - if it is, then it does not include that element in the answer and just recurses on xs
             - if it is not, then it does get included along with the recursion on the rest of the list

       - uniquify will keep the *last* occurrence of a repeated element because it will continue to skip it until it doesn't find it in the rest of the list
          - In particular
             uniquify [1, 2, 3, 1, 4, 2, 3]
       
             yields [1, 4, 2, 3] corresponding to the last occurrences of these

          - if we had kept the first occurrences it would have been [1, 2, 3, 4] (you'll have to code this up for the assignment :)

  • look at the mystery function in map_mystery.sml code
       - What is its type signature?
          ('a -> 'b) -> 'a list -> 'b list

          - Note that the first argument is a function because we're using it in the context of a function, i.e. (f x), a function call

       - What does it do?
          - For each element in the list, calls the function with that element and adds the result of that function call to the result.

       - For example:
          > fun negate x = ~x;
          val negate = fn : int -> int
          > negate 7;
          val it = ~7 : int
          > mystery negate [1, 2, 3, 4];
          val it = [~1,~2,~3,~4] : int list

  • map
       - the mystery function is so important (and common) that it is built into sml as a function call map
       - very powerful function
       - if you feel like you want to "loop" over a list, map can often be used instead of writing an additional recursive function.

  • Another map example
       - I want to write a function called calc_areas that takes a list of (base, height) tuples representing the sides of rectangles and then calculates the area of these rectangles
          - What will be the type signature?

          val calc_areas = fn: (int * int) list -> int list

       - Let's do this *using map*?
       - First, let's write a function that just calculates the area given a tuple, calculate the area:
          fun mult (w, h) = w*h;
       - Now, Let's put it together using map
          > fun calc_areas lst = map mult lst;
          val calc_areas = fn : (int * int) list -> int list

          > calc_areas [(1, 2), (4, 5), (6, 2)];
          val it = [2,20,12] : int list

  • One last example:
       - What does the following declare?
          > val calc2 = map mult;

       - Map is curried and we can just instantiate the first argument
       
       - Two ways to think about this:
          1) the type signature hints at what the function does:
             ('a -> 'b) -> 'a list -> 'b list

             is instantiated with the mult type signature (int * int) -> int resulting in:

             (int * int) list -> int list

          2) alternatively, if we look at the mystery function in map_mystery.sml code and think about hard-coding f to "mult", we see that we get a function that takes a list of tuples, applies mult and concatenates the answer

       - This is just another way of defining calc_areas!


  • time saver: op
       - In the example above, we defined a new function mult. It 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;



  • anonymous functions
       - sometimes we only need a function for one simple use case, e.g. when we were trying to add 47 to all the elements in a list
       - we could write the function as:
          fun add47 x = x+47;

       - and then use it in map like we did
       - we can also just create a function on the fly, what is called an anonymous function (because it doesn't have a name!)
       - the syntax is:
          fn <parameters> => <body>
       - for example, we could write the add47 function as:

          fn x => x+47;

       - We can embed this in a statement, for example:
          fun add47TOAll lst = map (fn x => x+47) lst
       
       - or even more interesting
          fun addToAll k lst = map (fn x => x+k) lst

       - or if you want to get fancy
          val addToAll k = map (fn x => x+k)