CS52 - Spring 2016 - Class 20

Example code in this lecture

   unit.sml
   nonlazy_lists.sml
   lazy_lists.sml

Lecture notes

  • admin
       - midterm
          - average: 23.6 (81%)
          - Q1: 22 (77%)
          - Q2 (median): 23.6 (82.5%)
          - Q3: 26 (91.2%)

       - course registration
          - It will work out!
          - Likely two more Pomona electives that haven't been posted yet

  • Pseudorandom number generation (and the seed)

  • SML for the day: the "unit" type
       - only has one value
          - carries no information at all!
       - represented by "()", e.g.
       
          > ();
          val it = () : unit
       
       - we can use it just like we would any other type
          - get comfortable with it as a type
       - look at the first three functions in unit.sml code
          - what are their type signatures?
             - somethingFromNothing
                - unit -> int
             - censor
                - 'a -> unit
             - censorNumber
                - int -> unit

          - what do they do?

          - These all take as parameters or return as parameters the unit type:
             > somethingFromNothing ();
             val it = 10 : int
             > censor 10;
             val it = () : unit
             > censor "banana";
             val it = () : unit
             > censorNumber 10;
             val it = () : unit
             > map censor [1, 2, 3, 4];
             val it = [(),(),(),()] : unit list
             > map censor (explode "banana cream pie");
             val it = [(),(),(),(),(),(),(),(),(),(),(),(),...] : unit list
       
       - look at the unitList function in unit.sml code
          - What is its type signature?
             - int -> unit list
          - What does it do?
             - generates a unit list of size n

             > unitList 10;
             val it = [(),(),(),(),(),(),(),(),(),()] : unit list
             > map somethingFromNothing (unitList 15);
             val it = [10,10,10,10,10,10,10,10,10,10,10,10,...] : int list

  • creating our own lists
       - look at the cs52list datatype in nonlazy_lists.sml
          - What is it?
             - the list data type!
             - a cs52list is either:
                - Nil (i.e. empty), or
                - a value and another cs52list

          - How would I create the list [1, 2, 3] as a cs52list?
             > Cons(1, Cons(2, Cons(3, Nil)));
             val it = Cons (1,Cons (2,Cons #)) : int cs52list
             
             Note that long lists (and recursive structures in general) won't print more than a few depths in

             - we could also create it one entry at a time

             > val prev = Cons(3, Nil);
             val prev = Cons (3,Nil) : int cs52list
             > val prev = Cons(2, prev);
             val prev = Cons (2,Cons (3,Nil)) : int cs52list
             > val prev = Cons(1, prev);
             val prev = Cons (1,Cons (2,Cons #)) : int cs52list

       - Write two functions that use the cs52list datatype
          - first: gets the first elements from a cs52list
             'a cs52list -> 'a
          - rest: get the rest of list
             'a cs52list -> 'a cs52list

       - look at first and rest in nonlazy_lists.sml code
          - if called on Nil
             - raise ListEmpty exception
          - if called on a non-empty list
             - use pattern matching
             - for data types, use the constructor

          > first prev;
          val it = 1 : int
          > rest prev;
          val it = Cons (2,Cons (3,Nil)) : int cs52list

  • what does the countUp function do?
       - what is its type signature?
          int -> int cs52list
       
       - creates a list of integers starting at k
       - and ending at?
          - doesn't end!
       - We can declare this function, but if we try and run it sml will get into infinite recursive calls!

  • delaying evaluation
       - what's the difference between these two declarations?
          val x = some-expression;
          val y = fn () => some-expression;
          fun z () = some-expression;

          - the first one declares x to be whatever the result is of some-expression
          - the second two declare y and z to be functions that, when called, evaluate some-expression
       
          - for example:
             > val x = 10;
             val x = 10 : int
             > val y = fn () => 10;
             val y = fn : unit -> int
             > x;
             val it = 10 : int
             > y ();
             val it = 10 : int
             
       - what if some-expression takes a long time to calculate? Will all three take the same amount of time to declare?
          - No!
          - in declaring x, some-expression is evaluated
          - in declaring a function, some-expression isn't evaluated until the function is called
       - for example, we can declare:
          > val y = fn () => countUp 0;
          val y = fn : unit -> int cs52list

             - if we call it, we'll be in trouble, but we can declare it
             - by wrapping the expression inside a function we have delayed the evaluation of that function
       - in contrast, if we tried to just declare a value, we'd have problems
          > val x = countUp 0;

  • defining lazy lists
       - we can use this idea to create data structures that only evaluate their arguments when needed
       - look at the data type declaration in lazy_lists.sml code
          - looks almost identical to the cs52list
             - it is a list structure
          - how is it different?
             - second part of the tuple is (unit -> 'a lazy_list) instead of just 'a lazy_list
             - what is that?
                - it's a function that takes as a parameter ()
                - and returns 'a lazy_list
       - how can we create a lazy list with the numbers 1, 2, 3?
          - might try it like we did before:
             > LazyCons(3, LazyNil);
             stdIn:103.1-103.13 Error: operator and operand don't agree [tycon mismatch]
              operator domain: int * (unit -> int lazy_list)
              operand: int * 'Z lazy_list
              in expression:
              LazyCons (3,LazyNil)

             SML is complaining that it expected something int * (unit -> int lazy_list), but got int * 'Z lazy_list
          - what's the problem?
             - the second argument to the constructor should be a function!

          > LazyCons(3, fn () => LazyNil);
          val it = LazyCons (3,fn) : int lazy_list
          > val prev = LazyCons(2, fn () => prev);
          val prev = LazyCons (2,fn) : int lazy_list
          > val prev = LazyCons(1, fn () => prev);
          val prev = LazyCons (1,fn) : int lazy_list

       - write the functions lazyFirst and lazyRest that get the first and rest of a lazy_list
          - they will be *almost* identical to our previous list implementation
          - lazyFirst: 'a lazy_list -> 'a
          - lazyRest: 'a lazy_list -> 'a lazy_list

       - look at the lazyFirst and lazyRest functions in lazy_lists.sml code
          - only one difference between the nonlazy versions!
          - why "xs ()" in lazyRest?
             - remember the datatype is 'a * (unit -> 'a lazy_list)
             - if we want to get at the "rest" we need to call the function!

  • what does this buy us?
       - look at the countUp function in lazy_lists.sml code
          - almost identical to our countUp function for normal lists
          - two differences:
             - uses LazyCons (so uses lazy lists)
             - and, because it's a lazy list, the second argument is a function that takes the unit type as input
       
          - what is the type signature?
             int -> int lazy_list
          
          - how does this help us?
             - SML evaluates all the arguments to a function call
             - in the original list, this meant making the recursive call and continuing
             - in this case, the expression countUp (k+1) evaluates to a new function and the recursion does NOT continue

       - using the countUp function we can define a new variable:
          > val naturals = countUp 0;
          val naturals = LazyCons (0,fn) : int lazy_list

          - Why didn't this cause infinite recursion?
             - we see the first value there (0)
             - the "rest" of the list is a function call
       - what does naturals represent?
          - all the natural number (0, 1, ...)
          - they're all there :)

          > lazyRest naturals;
          val it = LazyCons (1,fn) : int lazy_list
          > lazyRest (lazyRest naturals);
          val it = LazyCons (2,fn) : int lazy_list

          - sort of...
             - it relies on lazy evaluation
             - each time we call lazyRest we're essentially computing k+1

  • look at the lazyFind method in lazy_lists.sml code
       - what is its type signature?
          'a -> lazy_list -> 'a
       - what does it do?
          - tries to find a value in a lazy_list and then returns it
          - what does the else clause do?
             - again, since xs is a function, we need to call xs to get to the associated "next" item in the list
             - why can't we just call lazyRest?
                - lazy rest expects a lazy_list as a parameter
                - xs is of type (unit -> 'a' lazy_list)

       - using this function we can search for any number in a lazy_list
          > lazyFind 10 naturals;
          val it = 10 : int
          > lazyFind 1000 naturals;
          val it = 1000 : int
          > lazyFind 91298131 naturals;
          val it = 91298131 : int

          - they're all there :)
       - what would "lazyFind ~1 naturals" do?
          - infinite recursion!
          - going to keep trying to find it by looking at larger numbers but will never find it.

  • we want to write a function called lazyCopyFirstN which takes in a lazy_list and copies the first n elements
       - the critical part is that during recursion, we want to do something like:
       
          LazyCons(x, lazyCopyFirstN (n-1) ?)

          - the question is what goes there?
       - can we put (xs ()) like before?
          - no!
          - it is a lazy_list, which works for the call to lazyCopyFirstN
          - the problem is that lazyCopyFirst returns a lazy_list
             - we need a (unit -> lazy_list)

       - we want a function that:
          - takes () as input
          - and returns the lazy_list that is the result of lazyCopyFirstN (n-1) (xs ())
             (fn () => lazyCopyFirstN (n-1) (xs ()))

       - a better way of writing this
          - lazyCopyFirstN is a curried function, so lazyCopyFirstN (n-1) gives back a function
          - what is the type of lazyCopyFirstN (n-1) ?
             - lazy_list -> lazy_list
          - what is the type of xs?
             - unit -> lazy_list
          - what is the type of (lazyCopyFirstN (n-1)) o xs ?
             - this is equivalent to
                1) first, calling xs with some unit argument
                2) then, whatever the result is, passing that as the argument to (lazyCopyFirstN (n-1))
             - xs's input is unit and the output of (lazyCopyFirstN (n-1)) is lazy_list, so
                unit -> lazy_list