CS52 - Spring 2016 - Class 4

Example code in this lecture

   list_of_lists.sml
   map_examples.sml

Lecture notes

  • admin
       - assignment 1 due Friday at 5pm
       - assignment 2 out tomorrow
          - you can start already!   
       - academic honesty
          - be mindful!
       - class participation

  • SML debugging/programming
       - SML requires thinking NOT hacking
       - Work through the recursion by hand and/or small problems by hand (e.g. for comb1 and combList)
       - Check your recursion by calculating by hand the recursive case and then trying out the recusion, e.g:
          - rev [1, 2, 3, 4]         - you guess that the recursive case is:
             - rev (x::xs) = (rev xs)::[x]
             - You can work though it by hand: rev [1, 2, 3, 4]
             - x = 1
             - xs = [2, 3, 4]
             - assuming rev works correctly (rev xs) => [4, 3, 2]
             - we can now type the recusrive case:
                - [4, 3, 2] :: [1]
                stdIn:7.1-7.17 Error: operator and operand don't agree [literal]
                 operator domain: int list * int list list
                 operand: int list * int list
                 in expression:
                 (4 :: 3 :: 2 :: nil) :: 1 :: nil

  • What does the following function do?
       fun mystery [] = false
        | mystery [x] = true
        | mystery (x::y::xs) = mystery xs;

       - What is the type signature of this function?
          val mystery = fn: 'a list -> bool

       - What are the three patterns matching?
          - []: empty list
          - [x]: a list with one element
          - (x::y::xs): a list with at least two elements in it
             - x gets the first element
             - y get the second element

       - What does it do?
          - returns true if the list has an odd length, false if it has an even length

  • match nonexhaustive
       - What if we'd mistakenly typed:
       fun mystery [] = false
        | mystery (x::y::xs) = mystery xs;

       i.e. left off the second pattern?

       - It will compile, but SML will issue a warning!
          mystery2.sml:1.6-2.36 Warning: match nonexhaustive
        nil => ...
        x :: y :: xs => ...

           val mystery = fn : 'a list -> bool

       - What's the issue?
          - We cannot call mystery with every type of list
          - In particular, if we were to call it with an odd length list, we'd get to a point where there wasn't a pattern to match!

       - Be careful! Sometimes these can be subtle. Anything wrong the following function?
          fun ex 0 [] = 0
           | ex y (x::xs) = (ex (y+x) xs);

          - Still match nonexhaustive
             - missing the case where the first parameter is non-zero and the second parameter is the empty list
             - matches require *both* things to match (i.e. like "and")

  • warnings in SML
       - In general, most warnings you CANNOT ignore
          - Almost all of them point at a problem with your code

       - there are two exceptions, i.e. warnings you *can* ignore:
          > [] = [];
          stdIn:13.4 Warning: calling polyEqual
          val it = true : bool

          > [] @ [];
          stdIn:11.1-11.8 Warning: type vars not generalized because of
           value restriction are instantiated to dummy types (X1,X2,...)
          val it = [] : ?.X1 list

  • write a function appendAll that takes a list of lists as a parameter and appends them all into a single list
       - type signature?
          - val appendAll = fn: 'a list list -> 'a list

       - look at appendAll function in list_of_lists.sml code
       
       - why @ and not ::?
          - x is a list that we want to add to, not treat x as an individual element

  • infiltrate
       - Let's write a function called infiltrate:
          - it takes two parameters, a number and a list and inserts that number into all possible positions in the list, e.g.
          > infiltrate 0 [1, 2, 3];
          val it = [[0,1,2,3],[1,0,2,3],[1,2,0,3],[1,2,3,0]] : int list list
          > infiltrate "a" ["b", "c", "d"];
          val it = [["a","b","c","d"],["b","a","c","d"],["b","c","a","d"],["b","c","d","a"]] : string list list


       - Walk through the four recursion steps:

       1. function header/type signature
          val infiltrate = fn: 'a -> 'a list -> 'a list list

       2. recursive case
          - infiltrate x (y::ys) => ?

          - What would we get if we called (infiltrate x ys)?
             - A list of lists with x inserted into all possible positions of ys
             - e.g. infiltrate 0 [2, 3]
                - [[0, 2, 3], [2, 0, 3], [2, 3, 0]]

          - Given this answer, how could we get our answer to the bigger problem (e.g. infiltrate 0 [1, 2, 3])?
             - We're missing the 1 from all of the examples:
                [[0, 2, 3], [2, 0, 3], [2, 3, 0]] =>
                [[1, 0, 2, 3], [1, 2, 0, 3], [1, 2, 3, 0]]

             - are we done?
                - missing [0, 1, 2, 3]

          - How can we do this?
             (x::y::ys) :: (consAllk y (infiltrate x ys))

       3. base case
          - how is the problem getting smaller?
             - the list is getting smaller
             
          - when this is the case, often base case of []

          - What should infiltrate x [] give us?
             - [[x]]
             - why not [x]?
                - we're supposed to be returning a list of lists

       4. put it all together!
          - look at the infiltrate function in list_of_lists.sml code


  • map
       - SML has a built-in function called map, here is the type signature:
          fn : ('a -> 'b) -> 'a list -> 'b list
       - curried or uncurried? How many parameters?
          - curried
          - two parameters
       - What are the parameters?
          - ('a -> 'b)
             - some function!
          - 'a list
             - a list of things
       - Any guesses to what map does?
          - map applies the function to each of the elements in the list
       - For example:
          > fun negate x = ~x;
          val negate = fn : int -> int
          > negate 7;
          val it = ~7 : int
          > map negate [1, 2, 3, 4];
          val it = [~1,,~2,~3,~4] : int list

  • 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 area (w, h) = w*h;
       - Now, Let's put it together using map
          > fun calc_areas lst = map area 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 mystery function below do (and what is its type signature)?
          fun add a b = a + b;
          fun mystery lst = map (add 47) lst;

       - First, what does (add 47) give us?
          - it gives us a function that takes a single argument (and int) and add 47 to it
          - for example,
             > val add47 = add 47;
             val add47 = fn: int -> int
       - We could have also written this:
          > val mystery = map (add 47);
          val mystery = fn: int list -> int list

          - Why?
             - map is a curried function
             - we've supplied the first argument therefore we're left with a function requiring a second argument, a list
             - how does SML know it's an int list?
                - (add 47) is a function int -> int

  • see all these map examples in map_examples.sml code

  • Our own map
       - What does this function do:

          fun mystery f [] = []
           | mystery f (x::xs) = (f x) :: (mystery f xs);

       - It's the map function!

       - Notice a few things:
          - even though map is very powerful, it's very easy to write in SML
          - functions are first order values so we can make them parameters (f above) and use them

  • 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)