CS52 - Fall 2015 - Class 19

Example code in this lecture

   unit.sml
   nonlazy_lists.sml
   lazy_lists.sml

Lecture notes

  • admin
       - midterm
          - average: 28.7 (78%)
          - max: 35.5 (96%)
          - Q1: 25 (67%)
          - Q2 (median): 30.5 (82%)
          - Q3: 32.75 (89%)
       - Assignment 6 back on Tuesday (sorry for the delay!)
       - Assignment 8 is posted
       - Office hours today: no office hours 3-3:30


  • Parsing binary expressions
       - EBNF for binary

       - 110 & 100 | 011
          - draw tree

       - 110 & 100 & 011
          - two possible trees!
             - &, | and ^ will all left associate
             - -> will right associate
       
       - !110 & (101 | 100)

  • Work left to right
       - can be done in a single pass
       - three main phases:
          - tokenization (scan): characters to tokens
          - parsing (parse): tokens to a syntax tree
          - evaluation (eval): syntax tree to a value

  • parsing
       - write at least one function for each production rule
          - e.g. byte, literal, etc.

       - should translate directly from the EBNF rules

       - each of these functions should:
          - take a list of tokens as input
          - consume as many tokens as needed for that particular component
          - return a tuple:
             1) the syntax tree from the created component
             2) a list of the remaining tokens that were not processed (i.e. were not part of this component)

  • Look at the disjunction function


  • 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), it won't print more than a few depths

             - 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 takes two parameters
       
          - what is the type signature?
             int -> unit -> int lazy_list

          - what does the recursive call return then?
             - it's a curried function
             - returns a function!
             unit -> 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.