We introduced Ivars, which are Deferreds that can be determined by the program
We implemented bind in terms of Ivars and upon
We implemented either in terms of Ivars and upon
Note: many of the functions you want to build using Ivars are already available; be sure to consult the
Deferred module first!
An Ivar is a Deferred that the program can determine. As with Deferreds, once an Ivar is filled, its value cannot be changed. The
Ivar module contains the following functions:
module Ivar : sig type 'a t (** creates an empty Ivar *) val create : unit -> 'a Ivar.t (** fills the Ivar if it is empty. Raises an exception if it is already full *) val fill : 'a Ivar.t -> 'a -> unit val is_full : 'a Ivar.t -> bool val is_empty : 'a Ivar.t -> bool (** Asynchronously read the value of an Ivar. result becomes determined when the Ivar is filled *) val read : 'a Ivar.t -> 'a Deferred.t end
read function converts an
Ivar.t into a
Deferred.t. In fact,
Deferreds are defined internally as being the same, but the two different interfaces allow the ability to write to a deferred to be encapsulated. For example, we could imagine having
bind return an
Ivar.t instead of a
Deferred.t, but this means that the caller to bind can misuse the value that gets returned by determining it before it should.
fill operation enforces the write-once constraint on deferred values; calling
fill on an
Ivar that is already full raises an exception.
As a first exercise, we implemented
let bind d f = let result = Ivar.create () in upon d (fun x -> upon (f x) (fun y -> Ivar.fill result y) ); Ivar.read result
This matches the description of
bind given in a previous lecture: we first create a new "box" (result), the schedule
f to be run when
d is determined. We also schedule a function to be run when
f's result is determined; this function fills the
result box. After scheduling these functions to run,
Suppose we wanted to createa deferred that becomes determined when either of two deferreds becomes determined. We can use
Ivars to do this:
type ('a,'b) choice = Left of 'a | Right of 'b let either d1 d2 = let result = Ivar.create () in upon d1 (fun x -> if Ivar.is_empty result then Ivar.fill result x else () ); upon d2 (fun y -> if Ivar.is_empty result then Ivar.fill result (Right y) else () ); Ivar.read result
This function creates a new Ivar, and registers a callback with each of
d2 that fills the result Ivar.
Two points that often cause confusion deserve mention. The first is that we don't have to worry about result becoming filled between the call to
Ivar.fill. This is because while our code is running, the scheduler cannot preempt us and cause anything else to run.
The second point is that we think of
either as returning the value of the "first" of
d2 that becomes determined. This is more or less true, but if
d2 become scheduled at nearly the same time, the scheduler may cause our callbacks to execute in a different order. We aren't allowed to reason about timing; our program cannot really determine which of two things happens first.
Note that this is a good way of thinking for advanced systems that are distributed across a network. Because of networking delays, dropped packets, poorly synchronized clocks, and other features of distributed programming, it is important not to try to reason about time when building distributed systems.
As an exercise, try implementing
either without using
Ivars. You will find that you cannot.