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.