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