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 $ 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 [](rec_code/ 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 ``. 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 *) 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 *) 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 *) 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 ``, 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 `` 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 ``. 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.