318 lines
10 KiB
Markdown
318 lines
10 KiB
Markdown
|
# Homework 5: Lazy Evaluation
|
||
|
|
||
|
*CSci 2041: Advanced Programming Principles, Spring 2017*
|
||
|
|
||
|
**Due:** 5pm Wednesday, April 5
|
||
|
|
||
|
|
||
|
## Part 1: Expression evaluation by-hand
|
||
|
|
||
|
In class, and demonstrated in the document "Expression Evaluation
|
||
|
Examples" on Moodle, we have evaluated expressions by hand, step by
|
||
|
step, to understand the different ways in which call by value
|
||
|
semantics, call by name semantics, and lazy evaluation work.
|
||
|
|
||
|
#### Question 1
|
||
|
|
||
|
Consider the following definitions:
|
||
|
```
|
||
|
sum [] = 0
|
||
|
sum x::xs -> x + sum xs
|
||
|
|
||
|
take 0 lst = [ ]
|
||
|
take n [ ] = [ ]
|
||
|
take n (x::xs) = x::take (n-1) xs
|
||
|
|
||
|
some_squares_from 0 v = [ ]
|
||
|
some_squares_from n v = v*v :: some_squares_from (n-1) (v+1)
|
||
|
```
|
||
|
|
||
|
Evaluate
|
||
|
```
|
||
|
sum (take 3 (some_squares_from 5 1))
|
||
|
```
|
||
|
using call by value semantics, call by name semantics, and lazy
|
||
|
evaluation.
|
||
|
|
||
|
Each of these three evaluations must be clearly labeled with the form
|
||
|
of evaluation used.
|
||
|
|
||
|
Furthermore, all must be produced electronically. No handwritten work
|
||
|
will be accepted. You may use a text editor and turn in a text file.
|
||
|
These are relatively simple so there is no need to use MarkDown or
|
||
|
generate a PDF file.
|
||
|
|
||
|
Your name and University Internet ID must appear in the upper left of
|
||
|
the document.
|
||
|
|
||
|
Name this file ``question_1.txt`` and place it in a directory named
|
||
|
``Hwk_05`` in your GitHub repository.
|
||
|
|
||
|
|
||
|
|
||
|
#### Question 2
|
||
|
|
||
|
Recall our definitions for ``foldl`` and ``foldr`` as well as the
|
||
|
functions for folding ``and`` over a list of boolean values. (Note
|
||
|
that we removed the underscores from the names as they appeared on the
|
||
|
slides.)
|
||
|
```
|
||
|
foldr f [] v = v
|
||
|
foldr f (x::xs) v = f x (foldr f xs v)
|
||
|
|
||
|
foldl f v [] = v
|
||
|
foldl f v (x::xs) = foldl f (f v x) xs
|
||
|
|
||
|
and b1 b2 = if b1 then b2 else false
|
||
|
|
||
|
andl l = foldl and true l
|
||
|
andr l = foldr and l true
|
||
|
```
|
||
|
|
||
|
You are asked to evaluate the expressions
|
||
|
```
|
||
|
andl (t::f::t::t::[])
|
||
|
```
|
||
|
and
|
||
|
```
|
||
|
andr (t::f::t::t::[])
|
||
|
```
|
||
|
using both call by value and call by name semantics.
|
||
|
|
||
|
A few things to note:
|
||
|
|
||
|
+ Since anytime a function uses an argument more than once
|
||
|
(this only happens with ``f`` in the two fold functions) it is given
|
||
|
a value and not an unevaluated expression, there is no benefit from
|
||
|
the optimization we get with lazy evaluation. So we don't need to
|
||
|
consider it here.
|
||
|
|
||
|
+ To save space we are not spelling out the full name of the values
|
||
|
``true`` and ``false`` but are instead abbreviating them here as
|
||
|
``t`` and ``f``. Do not consider these to be variables! They are
|
||
|
the boolean literals for true and false. You may do the same in
|
||
|
your homework.
|
||
|
|
||
|
+ Lists are written in their basic form using the cons (``::``) and
|
||
|
nil (``[]``) operators instead of the syntactic sugar form using
|
||
|
semicolons to separate lists values between square brackets.
|
||
|
|
||
|
|
||
|
Clearly label each evaluation with the kind of semantics used for it.
|
||
|
|
||
|
After you have completed all four of them, explain which one is the
|
||
|
most efficient and why.
|
||
|
|
||
|
Each of these three evaluations must be clearly labeled with the form
|
||
|
of evaluation used.
|
||
|
|
||
|
Furthermore, all must be produced electronically. No handwritten work
|
||
|
will be accepted. As before, you may use a text editor and turn in a
|
||
|
text file.
|
||
|
|
||
|
Your name and University Internet ID must appear in the upper left of
|
||
|
the document.
|
||
|
|
||
|
Name this file ``question_2.txt`` and place it
|
||
|
in the ``Hwk_05`` directory your created for question 1.
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## Part 2: Efficiently computing the conjunction of a list of boolean values in OCaml
|
||
|
|
||
|
Write an OCaml function named ``ands`` with the type
|
||
|
``bool list -> bool`` that computes the same result as the
|
||
|
``andl`` and ``andr`` functions described in the problem above.
|
||
|
|
||
|
Your OCaml function should be written so that it does not examine or
|
||
|
traverse the entire list if it does not need to. We saw this behavior
|
||
|
in one of the by-hand evaluations above. If a ``false`` value is
|
||
|
encountered then it the computation can terminate and return
|
||
|
``false``.
|
||
|
|
||
|
|
||
|
This function should, following the pattern of past assignments, be
|
||
|
placed in a file named ``hwk_05.ml``. This file must be placed in a
|
||
|
directory named ``Hwk_05`` in your GitHub repository.
|
||
|
|
||
|
|
||
|
|
||
|
## Part 3: Implementing Streams in OCaml
|
||
|
|
||
|
In class, and demonstrated in the file ``streams.ml`` in the
|
||
|
code-examples directory of the public repositories, we developed a
|
||
|
type constructor ``stream`` that can be used to create lazy streams
|
||
|
and make use of lazy evaluation techniques in a strict/eager language.
|
||
|
|
||
|
Below you are asked to define a collection of stream values and
|
||
|
functions over streams.
|
||
|
|
||
|
To start this part of the assignment, first copy the ``streams.ml``
|
||
|
file into your ``Hwk_05`` directory. Add the following functions to
|
||
|
the end of that file. But you should clearly mark the parts of this
|
||
|
file that you did not write and attribute them to their author (your
|
||
|
instructor) and then indicate where your work starts in the file by
|
||
|
adding you name and a comment to this effect.
|
||
|
|
||
|
#### ``cubes_from``
|
||
|
|
||
|
Define a function ``cubes_from`` with the type ``int -> int stream``
|
||
|
that creates a stream of the cubes of numbers starting with the
|
||
|
input value. For example, ``cubes_from 5`` should return a stream
|
||
|
that contains the values 125, 216, 343, ...
|
||
|
|
||
|
Demonstrate to yourself that this work by using ``take`` to generate a
|
||
|
finite number of cubes.
|
||
|
|
||
|
#### ``drop``
|
||
|
|
||
|
Write a function named ``drop`` with the type ``int -> 'a stream -> 'a
|
||
|
stream``. This function is the stream version of the ``drop``
|
||
|
function that you've written for lists. Note the difference of the
|
||
|
type for ``drop`` for that of ``take`` over lists. Since ``take``
|
||
|
will remove a finite number of elements from a stream we have decided
|
||
|
to store them in a list instead.
|
||
|
|
||
|
#### ``drop_until``
|
||
|
|
||
|
Write a function named ``drop_until`` with the type
|
||
|
``('a -> bool) -> 'a stream -> 'a stream`` that returns the "tail" of
|
||
|
a stream after dropping all of the initial values for which the first
|
||
|
argument function returns ``false``.
|
||
|
|
||
|
That is, this function keeps dropping elements of the input stream
|
||
|
until it finds one for which the function returns ``true``. This
|
||
|
element, and all those that follow it, form the stream that is
|
||
|
returned.
|
||
|
|
||
|
For example, using ``head`` and ``squares`` from our original
|
||
|
``streams.ml`` file
|
||
|
```
|
||
|
head (drop_until (fun v -> v > 35) squares)
|
||
|
```
|
||
|
should evaluate to ``36``
|
||
|
|
||
|
|
||
|
#### ``map``
|
||
|
|
||
|
In ``streams.ml`` we defined the functions ``filter`` and ``zip`` to
|
||
|
work over lists. You are now asked to define a ``map`` function over
|
||
|
steams with the type ``('a -> 'b) -> 'a stream -> 'b stream``. This
|
||
|
is the analog to ``map`` over lists.
|
||
|
|
||
|
|
||
|
#### ``squares``, again.
|
||
|
|
||
|
In the class examples we defined the stream of squares of natural
|
||
|
numbers using ``zip`` so that ``squares = zip ( * ) nats nats``.
|
||
|
|
||
|
We could also define ``squares`` as ``squares_from 1`` using the
|
||
|
function defined above.
|
||
|
|
||
|
Define ``squares_again`` to be equal to ``squares`` but define it
|
||
|
using the ``map`` function written above.
|
||
|
|
||
|
|
||
|
#### square roots
|
||
|
|
||
|
You've previously seen a recursive function and an imperative loop to
|
||
|
compute the square root of a floating point number to some specified
|
||
|
degree of accuracy.
|
||
|
|
||
|
We now make use of lazy evaluation to pull apart two aspects of this
|
||
|
algorithm
|
||
|
|
||
|
1. the part the generates a sequence of approximations to
|
||
|
the square root of a number, and
|
||
|
2. the part that determines when the approximations generated so far
|
||
|
are close enough that we can stop generating them
|
||
|
|
||
|
The **first task** is to define the function ``sqrt_approximations`` with
|
||
|
the type ``float -> float stream`` that takes a floating point number
|
||
|
and generates the stream of approximations to its square root. This
|
||
|
sequence corresponds to the sequence of values that the local variable
|
||
|
``guess`` took in a previous implementation of this algorithm.
|
||
|
|
||
|
For example, ``take 10 (sqrt_approximations 49.0)`` evaluates to
|
||
|
```
|
||
|
[25.; 13.; 7.; 10.; 8.5; 7.75; 7.375; 7.1875; 7.09375; 7.046875]
|
||
|
```
|
||
|
|
||
|
(Recall that floating point values are approximations or real numbers
|
||
|
and thus there may be small round off errors that cause your results
|
||
|
to be slightly different from these.)
|
||
|
|
||
|
The **second task** is to write a function whose purpose is similar to
|
||
|
the ``accuracy`` value used in the previous implementations, that is,
|
||
|
to determine when to stop generating approximations.
|
||
|
|
||
|
This function should be named ``epsilon_diff`` with the type ``float
|
||
|
-> float stream -> float``. The first argument is a floating point
|
||
|
value, perhaps named ``epsilon``. This function pulls values out of the
|
||
|
stream until the difference between two successive values in the
|
||
|
stream is less than epsilon. It then returns the second of those two
|
||
|
values.
|
||
|
|
||
|
An example below uses a ``stream`` named ``diminishing`` which begins
|
||
|
at ``16.0`` and each following value is half of its preceding on.
|
||
|
For example, in utop:
|
||
|
```
|
||
|
# take 10 diminishing ;;
|
||
|
- : float list = [16.; 8.; 4.; 2.; 1.; 0.5; 0.25; 0.125; 0.0625; 0.03125]
|
||
|
```
|
||
|
Define ``diminishing`` in your file.
|
||
|
|
||
|
We can use ``epsilon_diff`` to as follows:
|
||
|
```
|
||
|
# epsilon_diff 0.3 diminishing ;;
|
||
|
- : float = 0.25
|
||
|
```
|
||
|
Since the difference between ``0.5`` and ``0.25`` is the first one
|
||
|
that is less than ``0.3``, the second of them is returned.
|
||
|
|
||
|
|
||
|
As a **third task**, include the following declarations to compute the
|
||
|
square root of ``50.0`` to different degrees of accuracy:
|
||
|
```
|
||
|
let rough_guess = epsilon_diff 1.0 (sqrt_approximations 50.0)
|
||
|
|
||
|
let precise_calculation = epsilon_diff 0.00001 (sqrt_approximations 50.0)
|
||
|
```
|
||
|
|
||
|
#### another square root
|
||
|
|
||
|
The function ``epsilon_diff`` looked at two successive values to
|
||
|
determine when to stop traversing a stream.
|
||
|
|
||
|
We now want to write a square root approximation function that picks
|
||
|
the first element out of ``sqrt_approximations`` that is within some
|
||
|
threshold. This one looks at the elements of ``sqrt_approximations``
|
||
|
separately instead of comparing two of them in determining what value
|
||
|
to return.
|
||
|
|
||
|
This function, named ``sqrt_threshold``, has the type ``float -> float
|
||
|
-> float``. The first floating point value is the one we would like
|
||
|
to take the square root of, call it ``v``. The second is a threshold
|
||
|
value, call it ``t``. We want return the first element, say ``s``, of
|
||
|
``sqrt_approximations`` for which the absolute value of ``(s *. s)
|
||
|
-. v`` is less than ``t``.
|
||
|
|
||
|
For full credit this function should make appropriate use of the
|
||
|
functions you've already written and those in the ``streams.ml`` file
|
||
|
we developed in class.
|
||
|
|
||
|
This function, at first glance, seems to return more accurate
|
||
|
answers than our value of epsilon might suggest. For example,
|
||
|
```
|
||
|
sqrt_threshold 50.0 3.0
|
||
|
```
|
||
|
evaluates to ``7.125``.
|
||
|
|
||
|
Write a comment in your OCaml file just above the definition of
|
||
|
``sqrt_threshold`` that explains why this is.
|
||
|
|
||
|
|
||
|
|