$29
Introduction
This homework will highlight lazy evaluation. Note: Part 3 is the most
substantial part of the homework.
To start, create a directory in your personal repository named ``Hwk_05``.
You must place all files for this homework in this directory.
Part 1: Evaluating expressions by hand
As demonstrated in class, and in the document "Expression Evaluation
Examples" (``expr_eval.pdf`` in ``Resources`` on GitHub), 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
evens_from 0 v = [ ]
evens_from n v = v+v :: evens_from (n-1) (v+1)
```
Now, create a file in your homework directory called ``question_1.txt`` with
the following:
- Your name
- Your Internet ID
You will now evaluate the following expression by hand:
```
sum (take 3 (evens_from 5 1))
```
Similarly to the examples in ``expr_eval.pdf``, you must evaluate the
expression three times using three different semantics:
1. Call by value
2. Call by name
3. Lazy evaluation (also known as "call by need")
Label each of your evaluations clearly by evaluation strategy employed, perhaps
as in this example:
```
CALL BY VALUE
CALL BY NAME
LAZY EVALUATION
```
Please write plain text. We also will not accept handwritten work.
Question 2
Recall these definitions for ``foldl`` and ``foldr``, and the functions for
folding ``and`` over a boolean list. (Note: We removed the underscores from the
names as they appeared on the S4.1 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
```
Now, create a file in your homework directory called ``question_2.txt`` with
the following:
- Your name
- Your Internet ID
# Let's evaluate by hand
You will now evaluate these two expressions:
```
andl (true::false::true::true::[])
```
and
```
andr (true::false::true::true::[])
```
You must evaluate the expressions using only two different semantics:
1. Call by value
2. Call by name
Label each of your evaluations clearly by evaluation strategy employed, perhaps
as in this example:
```
andr - CALL BY VALUE
andr - CALL BY NAME
andl - CALL BY VALUE
andl - CALL BY NAME
```
A couple of notes 🎵:
+ Write lists in their basic form using the cons (``::``) and nil (``[]``)
operators instead of using semicolons to separate list values between square
brackets.
+ We don't consider lazy evaluation for this question. Why not? (Try it and
compare with your other results.)
# Which is better?
After you have completed all four evaluations (call by value and call by name
for both `andr` and `andl`), state which evaluation is most efficient and
briefly explain why.
# Reminder
As before, please write plain text. We also will not accept handwritten work.
Part 2: Efficiently computing the conjunction of a list of Boolean values
Create an OCaml file in your homework directory named ``lazy_and.ml``.
Write a function named ``ands`` with type ``bool list - bool`` which computes
the same result as the ``andl`` and ``andr`` functions described in the
problem above, except with the following quality:
Your function should avoid examining the entire list unless absolutely
necessary, hence the "lazy." (Hint: We saw this behavior in one of the by-hand
evaluations above.) Stated explicitly: If the function encounters a ``false``
value then it can terminate and return ``false``.
Part 3: Implementing Streams in OCaml
As demonstrated in class (``lazy.ml`` in ``Sample Programs`` on GitHub) we
developed a type constructor ``stream`` which can create "lazy" streams and use
lazy evaluation techniques within OCaml (which itself is a strict/eager
language).
Start this part by creating a file named ``streams.ml`` in your homework
directory and copying the following code (including comments) into it:
```
(* The code below is from Professor Eric Van Wyk. *)
(* Types and functions for lazy values *)
type 'a lazee = 'a hidden ref
and 'a hidden = Value of 'a
| Thunk of (unit - 'a)
let delay (unit_to_x: unit - 'a) : 'a lazee = ref (Thunk unit_to_x)
let force (l: 'a lazee) : unit = match !l with
| Value _ - ()
| Thunk f - l := Value (f ())
let rec demand (l: 'a lazee) : 'a =
force l;
match !l with
| Value v - v
| Thunk f - raise (Failure "this should not happen")
(* Streams, using lazy values *)
type 'a stream = Cons of 'a * 'a stream lazee
(* The code below is from YOUR NAME HERE *)
```
Change ``YOUR NAME HERE`` to your name.
***Proper attribution:** Always clearly mark all parts of your files that you
did not write, as in the code above, and attribute them to their author (your
instructor, for example). Then indicate where your work starts in the file by
adding your name and a comment to this effect, such as in the code above.*
You will now define a collection of stream values, and functions over streams.
3.1 ``cubes_from``
Define a function ``cubes_from`` with type ``int - int stream`` that creates
a stream of the cubes of numbers starting with the cube of the input value.
For example, ``cubes_from 5`` should return a stream that contains the values
125, 216, 343, ...
Demonstrate to yourself that it works by using ``take`` to generate a finite
number of cubes.
3.2 ``cubes_from_zip``
Copy the ``zip`` function from the ``lazy.ml`` file from class to your current
file (with proper attribution), then define a function ``cubes_from_zip`` with
the same type and behavior as ``cubes_from`` above, but which uses the ``zip``
function (maybe more than once) to construct the stream of cubes.
You may copy and use more functions besides ``zip`` from the ``lazy.ml`` file
from class, but you must use proper attribution.
3.3 ``cubes_from_map``
Now copy the ``map`` function from the ``lazy.ml`` file from class to your
current file (with proper attribution), and define a function named
``cubes_from_map`` with the same type and behavior as ``cubes_from`` above, but
which uses ``map``.
3.4 ``drop``
Define a function named ``drop`` with type ``int - 'a stream - 'a stream``.
This function should discard zero or more elements from the stream, and the
first argument indicates how many to discard ("drop").
*(Notice the type difference between ``drop``, and ``take`` (from ``lazy.ml``).
This is because ``take`` removes finite elements from a stream, so it stores
them as a list.)*
3.5 ``drop_until``
Write a function named ``drop_until`` with type
``('a - bool) - 'a stream - 'a stream``. This function should discard as
many consecutive initial elements as possible from the stream, and output the
remaining stream. The first argument (a function) indicates whether to keep an
element (``true`` means keep, ``false`` means drop).
Stated explicitly, this function drops elements from the input stream until it
finds one for which the function returns ``true``. It then outputs the
remaining part of the stream (including the "``true``" element it found).
Example (using ``head`` and ``nats`` from ``lazy.ml``):
```
head (drop_until (fun v - v 35) nats)
```
The above expression should give ``36``.
3.6 ``foldr``, ``and_fold``, and ``sum_positive_prefix``
Recall ``foldr`` from Part 1 above. For this question, you will define a new
``foldr`` function which uses streams instead of lists. Here are some hints:
+ Because streams have no "empty stream" or "nil" constructor, there is no
"base" value passed to your new ``foldr`` -- it takes only two arguments.
+ Your new ``foldr``'s type must include the ``lazee`` type somewhere, so it
can give a value without examining the entire stream.
For full points, you must do the following:
1. Write the ``foldr`` function with type annotations, including both input
types and the output type.
2. Write an OCaml comment above your ``foldr`` function explaining ``foldr``'s
type and briefly how your function works.
# ``and_fold``
Next, define a function named ``and_fold`` with type ``bool stream - bool``.
It attempts to determine if all elements in the stream are ``true``. If it
encounters a ``false`` element, it should return ``false``.
This function should simply pass some kind of "and" function, and the stream,
to your new ``foldr``.
# ``sum_positive_prefix``
Also, define a function named ``sum_positive_prefix``. It should use your
``foldr`` to add up all the positive integers in an ``int stream`` which appear
**before** any non-positive number.
# A concrete example
To help us think about the above functions, consider the following stream
examples:
```
let ns : int stream = zip ( - ) (from 1000) (cubes_from 1)
```
Notice that ``ns`` is an ``int stream`` computed by using subtraction to zip
together two streams; The first stream is all positive numbers starting from
1000, and the second stream is cubes starting from 1. In particular, this
stream has some positive numbers at the beginning, but then the numbers in the
stream become negative because second stream gets larger faster than the first
stream.
For example, evaluating ``take 15 ns`` yields this:
```
[999; 993; 975; 939; 879; 789; 663; 495; 279; 9; -321; -717; -1185; -1731; -2361]
```
With ``ns`` in mind, consider these two streams:
```
let are_positive ns = map (fun n - n 0) ns
let ns_positive : bool stream = are_positive ns
```
Here, ``ns_positive`` is a ``bool stream`` whose initial values are ``true``,
followed by an unbounded number of ``false`` elements.
Thus
```
and_fold ns_positive
```
will evaluate to ``false``, but
```
and_fold (are_positive (from 1))
```
will not terminate normally.
Similarly, ``sum_positive_prefix ns`` will evaluate to ``7020``, but
``sum_positive_prefix (from 1)`` will not terminate normally.
3.7 The Sieve of Eratosthenes, "``sieve``"
For this final question, we ask you to write a function which computes the
[Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes).
Here is some background:
# The Sieve
We can compute prime numbers by starting with the sequence (a stream) of
natural numbers beginning at 2 and using the following process:
1. Consider the first number in the sequence as a prime number.
2. Discard all multiples of the above number in the remaining sequence.
3. For resulting remaining sequence, go to step (1).
To see this process in action, consider this prefix of natural numbers starting
at 2:
```
2 3 4 5 6 7 8 9 10 11 ...
```
Starting at step (1), we consider 2 as prime for step (2) we discard all
multiples of 2 in the remaining sequence. Notice that because 3 is not a
multiple of 2, we won't discard it:
```
3 5 7 9 11 ...
```
We then essentially "cons" 2 onto the resulting sequence beginning at 3, which
gives us this:
```
Cons (2, 3 5 7 9 11 ... )
```
For step (3), we have the resulting sequence beginning at 3 as our new sequence
and go back to step (1) to repeat the process and get this:
```
Cons (2, Cons (3, 5 7 11 ... ))
```
You can see that we will repeat again, with 5 as the new prime and the
remaining sequence beginning at 7. This process repeats for each prime number
"discovered."
# The ``sieve`` function
Implement the above process as a function named ``sieve``, which has type
``int stream - int stream``. Here are some hints:
+ With your ``sieve`` and the ``from`` function from class, you should be able
to define the stream of prime numbers as ``let primes = sieve (from 2)``.
Thus, ``take 10 primes`` should evaluate to
``[2; 3; 5; 7; 11; 13; 17; 19; 23; 29]``.
+ You might consider a helper ``sift`` function which implements the process we
described above. For example, it could have type
``int - int stream - int stream`` and would output a stream which does not
contain any multiples of its input ``int`` argument. For example, using the
``from`` function from ``lazy.ml``, you could have ``sift 2 (from 3)`` output
the stream ``3 5 7 9 11 ...`` as we saw above.
Turning in your work
Push these files to your ``Hwk_05`` GitHub directory by the due date:
- ``question_1.txt``
- ``question_2.txt``
- ``lazy_and.ml``
- ``streams.ml``
Remember: If you use any other functions from ``lazy.ml`` that we developed in
class, then clearly denote them with proper attribution in your solution.
As in Homework 4, part of your score will be based on your code quality. (See
"Writing transparent code" in Homework 4.)
-------------------------------------------------------------------------------