CS52 - Spring 2017 - Class 4

Example code in this lecture


Lecture notes

  • admin
       - assignment 1 due Friday at 5pm
          - make sure to read the specification carefully!
       - assignment 2 out tomorrow
          - you can start already!   
       - academic honesty and collaboration
       - class participation

  • SML debugging/programming
       - SML requires thinking NOT hacking
       - Work through the recursion by hand and/or small problems by hand
       - Check your recursion by calculating by hand the recursive case and then trying out the recursion, 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

  • error messages in SML
       - You will get exceptions/errors in SML
       - They're not the most informative, but you'll get used to understanding them as time goes on
       - For example, say we remove the parentheses around (m+1) in the interval function in rev_examples.sml
       - When we type: use "rev_examples.sml", we get the follow errors
          [opening rev_examples.sml]
          val rev = fn : 'a list -> 'a list
          rev_examples.sml:23.13-23.27 Error: operator is not a function [literal]
           operator: int
           in expression:
           1 n
          rev_examples.sml:23.9-23.28 Error: operator and operand don't agree [overload]
           operator domain: 'Z * 'Z list
           operand: 'Z * 'Y
           in expression:
           m :: interval m + 1 n
          rev_examples.sml:19.5-23.28 Error: right-hand-side of clause doesn't agree with function result type [overload]
           expression: 'Z -> _ list
           result type: 'Y
           in declaration:
           interval = (fn arg => (fn <pat> => <exp>))

          uncaught exception Error
           raised at: ../compiler/TopLevel/interact/evalloop.sml:66.19-66.27

       - Start at the first error/exception (this isn't always perfect, but it's generally a good place to start)
       - SML gives you information about *where* the problem is:

          - rev_examples.sml is the file with the problem
             - 23.13 is where is starts (line 23, character 13)
             - 23.27 is where it ends (line 23, character 17)
       - What's wrong with this function?
          - Be careful about order of operations
          - Function binding has higher precedence than + (in fact, almost the highest precedence of anything, so be careful)

  • 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?
          - '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

  • look at the appendAll function in more_list_recursion.sml code
       - type signature?
          - 'a list list -> 'a list
          - how do we know that the first argument is a list of lists?
             - @ expects two lists as arguments
             - therefore x must be a list
             - therefore x::xs must bit a list of lists

       - what does the function do?
          - takes in a list of lists and creates one giant list with all of the elements in all of the lists (appends all the lists together)

  • look at the funPairs function in more_list_recursion.sml code
       - type signature?
          - 'a -> ('b * 'a) list -> 'b -> 'a
             - the 2nd parameter is a list of tuples by the pattern definition in the recursive case
             - u and x must be the same type because of u = x
             - the return type must be the same as v since we return it in the if statement
             - finally, since the function must always return the same type, we know that default (the first parameter), must be the same as v and the return type

       - what does the function do?
          - searches through the list of tuples (second argument) and tries to match x (the third argument) to the first entry of the tuple
          - if it finds one that matches, it returns the corresponding pair of the tuple
          - it not, it returns the default value
       - For example:
          > val grades = [("dave", 85), ("teran", 94), ("lisa", 92), ("chris", 83)];
          val grades = [("dave",85),("teran",94),("lisa",92),("chris",83)] : (string * int) list
          > funPairs 0 grades "dave";
          val it = 85 : int
          > funPairs 0 grades "chris";
          val it = 83 : int
          > funPairs 0 grades "carl";
          val it = 0 : int

       - What is the "_" in the first parameter?
          - It's still just a variable name
          - We use it to communicate that in that pattern, that parameter doesn't get used

  • what does the sumList function do?
       - sums up all of the values in a list of numbers (ints)

  • look at the gradeStatus function in more_list_recursion.sml code
       - Another example of the let expression
          - the syntax is:

                declaration 1;
                declaration 2;

             where the "declaraion"s are either val or fun declaration.
          - Can serve multiple purposes:
             1) those declarations are only visible within the let expression, so it is a form of information hiding (like private in Java)
             2) allows you to break up computation into sequential steps when this is required
             3) can allow you to decompose values (e.g. to get at the values in a tuple, like in the 2nd declaration in the example)

       - ListPair.unzip is the opposite of ListPair.zip
          - ('a * 'b) list -> 'a list * 'b list

       - the function operates by
          1) use funPairs to extract the score for the student
          2) get the names as a list and the grades as a list so that
          3) calculate the average of all of the scores
          4) finally, the value returned is a string indicating whether the individual score is above or below the average

  • look at the mystery function in map_mystery.sml code
       - the way that we indicate that a parameter is a function is we use it like a function
          - (f x) implies a function call!

       - A few other examples
          fun temp f = (f 10) + 2;
          (int -> int) -> int

          - f is a function since we're calling it on the right
          - the input to the function is an int (10 in this case)
          - the output must also be an int since we're using it with "+ 2"

          fun temp2 myF = if myF "banana" then 1 else 2;
          (string -> bool) -> int

          - again, myF is a function
          - myF takes as input a string ("banana")
          - the return type for myF has to be a bool because we're using in the if clause