Part 1: Working with async
==========================
Async in utop
-------------
**Note**: don't forget to create a .ocamlinit and a .cs3110 file as explained in
the last lab.
Utop has rudimentary support for evaluating deferred expressions. When you
enter an expression of type `'a Deferred.t` into utop, utop will run the async
scheduler until the deferred becomes determined, and print out the contents of
the deferred instead of the deferred itself.
Unfortunately, there is a bug in the version of utop that was distributed with
the 3110 VM that prevents this feature from working properly. You can install
a patched version of utop by executing the following commands in the terminal:
```
$ opam pin add utop https://github.com/cs3110/utop.git
$ opam update utop
$ opam upgrade utop
```
This will download, compile, and install the patched version of utop.
**Exercise**: after running the above commands, run `utop`. Type the expression
`after (Core.Std.sec 2.);;`. Observe the output. Evaluate the expression
`after (Core.Std.sec 2.) >>= fun () -> return 3;;` and observe the output.
Utop does **not** continuously run the scheduler in the background. It only
runs the scheduler when you type in an expression with type Deferred.t, and it
stops running the scheduler when that deferred becomes determined.
**Exercise**: In utop, evaluate the expression
```
upon (after (Core.Std.sec 1.)) (fun () -> Printf.printf "hello\n");;
```
You will not see any output when the `after` deferred becomes determined,
because the scheduler is not running. If you later cause the scheduler to run
(by evaluating a deferred), you will see the output. Evaluate `after
(Core.Std.sec 0.);;`.
**Exercise**: typing `Core.Std.sec` and `after (Core.Std.sec 0.);;` can be
tiresome. Add the following to your `.ocamlinit` file (in addition to the
lines suggested in the previous recitation):
```
let sec = Core.Std.sec;;
let flush () = after (sec 0.);;
```
Restart utop. Evaluate
```
utop# upon (after (sec 1.)) (fun () -> Printf.printf "hello\n");;
utop# flush ();;
```
Testing with async
------------------
Writing unit tests for async code is difficult, because our unit testing
framework only works with non-deferred values, while async code usually
produces deferred values.
There is a function that can be used to allow non-async code (like the testing
framework) to interact with async code. It is called
`Thread_safe.block_on_async`.
`Thread_safe.block_on_async` has type `(unit -> 'a Deferred) -> ('a,exn)
Core.Std.Result.t`. `Thread_safe.block_on_async f` runs the async scheduler
until the deferred that `f` returns becomes determined with value `v`. It then
evaluates to `Core.Std.Result.Ok v`. If `f` raises an exception `e`, then
`block_on_async` evaluates to `Core.Std.Result.Error e`.
**You must NEVER use `block_on_async` except in your TESTs**. If you are tempted
to do so, you should use `(>>=)` instead.
You can use `block_on_async` to implement unit tests. For example, to test
that the deferred expression `f 1` returns `2`, you can first use `block_on_async`
to block until `f 1` becomes determined. Then you can compare the output to
`Core.Std.Result.Ok 2`:
```
TEST =
Thread_safe.block_on_async (fun () -> f 1) = Core.Std.Result.Ok 2
```
Of course, this is rather ugly, so you may want to create a helper function to
do these checks for you:
```
let test_async_eq (d : 'a Deferred.t) (v : 'a) : bool =
Thread_safe.block_on_async (fun () -> d) = Core.Std.Result.Ok v
```
Then the above test just becomes
```
TEST = test_async_eq (f 1) 2
```
**Exercise**: The file [testing.ml](rec_code/testing.ml) contains a simple
function for deferred addition. Complete the "check add" unit test. Compile
it, and then run it with `cs3110 test`.
One way a deferred computation can fail is by never determining its output. In
The function `with_timeout` takes a time span `t` and a deferred `d`, and
returns a deferred that becomes determined with value `` `Result v`` if `d`
becomes determined with value `v` before `t` seconds have elapsed. Otherwise,
it becomes determined with value `` `Timeout``.
**Exercise**: Use the `test_async_eq` function from above to test the function
called `broken` in `testing.ml`. Compile and run the test, note the behavior
(as always, you can stop the program with control+C if needed). Create a new
function `test_async_eq_with_timeout : 'a Deferred -> 'a -> bool`, and use it
to test both `broken` and `add`. `add` should pass the test, but `broken`
should not.
Part 2: Functional observer pattern with Ivars
==============================================
A common pattern that arises in asynchronous programs is that different modules
are responsible for interacting with different parts of the outside world.
For example, when developing an networked graphical adventure game, you might
have a `Player` module responsible for keeping track of a player's position, a
`Network` module responsible for communicating over the network, and a `Gui`
module responsible for keeping the GUI up to date.
When the state contained in the `Player` module changes (for example if the
user decides to move to a different room), both the Gui and the Network need to
respond (the Network module might broadcast the player's new position, while the
Gui module might change where the player is drawn on the screen).
One way to accomplish this would be to have the `Player` module notify the
other modules by calling update functions on them. This design can lead to
complicated code, because the `Player` module needs to know about all of the
modules that are using it. If you decided to add a `Logging` module in the
future that keeps a log of all of the game changes, you would have to modify
the code in the player module to call functions in the `Logging` module. The
`Player` module would be tightly coupled with many parts of the system.
An alternative approach is a functional version of the "observer pattern". The
`Player` module can expose a deferred value that becomes determined when the
state of the player changes. The modules that are interested in responding to
changes in the player state can bind a callback to this deferred that responds
appropriately to the changes in the player state.
The observer pattern is not specific to async. It is commonly employed in
object oriented programs. It is also related to other functional programming
paradigms called "flow based programming" and "functional reactive programming".
### Guidelines for implementing the subject module
The module that implements the changing state is called the "subject
module". In the adventure game example, `Player` would be the subject module.
The subject module will typically contain a "state type" representing the
changing state. For example, the `Player` module might define a `Player.t`
type to represent a single player.
```
(* in player.mli *)
type t
val position : t -> Vector.t
val inventory : t -> Item.t list
val mood : t -> Mood.t
val updated : t -> t Deferred.t
```
- The state type will typically be a record containing the important variables
(such as `position`, `inventory` and `mood` in the adventure game example).
It will also contain an `updated` Ivar that will be filled when the state
changes:
```
(* inside player.ml *)
type t = {
position : Vector.t;
mood : Mood.t;
inventory : Item.t list;
updated : t Ivar.t;
}
```
- Although we think of the state as changing over time, it is simpler to
implement the state as an immutable data structure; to "update" a
`Player.t`, we create a new record, and fill the `updated` Ivar of the old
record with the new version:
```
(* inside player.ml *)
let handle_keypress () =
...
Ivar.fill player.update {
position : player.position;
mood : new_mood;
inventory : player.inventory;
updated : Ivar.create ();
}
```
- It can be cumbersome to write this code every time you want to update the
player; it is likely that most of the functions in the Player module will
update player values often. It is useful to factor the update into a
separate function that takes a state with new fields but the old `updated`
Ivar, and takes care of filling the Ivar and creating the new updated Ivar:
```
(* inside player.ml *)
let do_update player =
let new_player = {player with updated = Ivar.create()};
Ivar.fill player.updated new_player;
new_player
```
With `do_update` defined, you can conveniently update the fields using
functional record update:
```
let handle_keypress () =
...
do_update {player with mood = Grumpy}
```
**Exercise**: The file [student.mli](rec_code/student.mli) contains the interface
for a student type (`Student.t`). Students can either be sleeping or working
(these are the only options). The `sleep` function changes a student from working
to sleeping, while the `work` function wakes the student up.
- Implement the `Student` module in `student.ml`, using the guidelines given
above.
- In your working file for the recitation, create a student `s`. Use `upon`
to schedule a message to be printed if the student goes to sleep.
- In utop, `#use` your working file. Call `Student.sleep` on your student to
cause the student to sleep, and observe the output (don't forget to call
`flush` to see the output). Call `Student.work` and then call
`Student.sleep` again. Notice that there is no output.
### Implementing observers
There is no output on the second call to `sleep` because you have only called
`upon` on the first version of the student, so your function only gets called
the first time the student is updated.
If you want to implement an observer function that gets called for _every_
change in the subject, you need to schedule it recursively. For example:
```
let rec observe_player player =
upon (updated player) (fun player' ->
observe_player player';
printf "player's new mood is %s\n" (Mood.to_string player'.mood)
)
```
This way, whenever the player is updated, the new version of the player is also
observed for future updates.
**Exercise**: modify your working file for the recitation so that a message is
printed _every_ time `s` falls asleep. In utop, call `sleep` and `work` on `s`,
and observe the output. If you don't see any output, don't forget to flush!
**Exercise (\*) **: The file [studentSet.mli](rec_code/studentSet.mli) contains an
interface for a set of students. A student set keeps track of a collection of
students.
1. Implement the `StudentSet` module
- Create a skeleton implementation of `studentSet.ml`. Implement all of the
functions with `failwith "TODO"`. Define the `StudentSet.t` type as a record
containing a list of sleeping students, a list of awake students, and an
Ivar for tracking changes.
- Implement `create`, `all_students`, `sleeping_students`, and `working_students`
- Implement `add_student`. Be sure to schedule a function to update the
StudentSet as necessary when the student changes.
2. Use the `StudentSet` module
- in your working file for the recitation, create a
StudentSet. Create two students and add them to the
StudentSet.
- As in the previous exercise, schedule a function to print
a message whenever one of your students falls asleep.
- Schedule a function to print a message whenever all of the students in
the student set fall asleep. The message should only be printed when there
were students who were awake. For example, if there were no working
students and a new sleeping student is added, then the message should not
be printed.
- In utop, experiment with putting your students to sleep and waking them up.