So far, we have seen how to describe concurrent computations using
the simple primitives of `bind` (often
written `>>=`) and `return`. The `return`
function takes a value and returns a deferred computation that is
determined immediately, while `d >>= f` waits
for `d` to become determined, and then passes the resulting
value to `f`, which also computes a deferred value. In general,
there may be many deferred computations executing concurrently, and we
will need to coordinate and synchronize their behavior.

These notes build on material and code examples originally developed in Real World OCaml Chapter 18.

A simple way to coordinate two deferred computations is to wait for
both of them to become determined. The function `both` does
this:

val both : 'a Deferred.t -> 'b Deferred.t -> ('a * 'b) Deferred.tIt is easy to implement

let both (d1:'a Deferred.t) (d2:'b Deferred.t) : ('a * 'b) Deferred.t = d1 >>= (fun v1 -> d2 >>= (fun v2 -> return (v1,v2)))or

let both (d1:'a Deferred.t) (d2:'b Deferred.t) : ('a * 'b) Deferred.t = d2 >>= (fun v2 -> d1 >>= (fun v1 -> return (v1,v2)))

More generally, we may want to wait for a list of deferred values to become determined:

val all : ('a Deferred.t) list -> ('a list) Deferred.tOne can think of

let rec all (l:('a Deferred.t) list) : ('a list) Deferred.t = List.fold_right (fun x acc -> x >>= (fun h -> acc >>= (fun t -> return h::t))) l (return [])

There is a dual to `all` called any that waits
for *some* value in a list of deferreds to become determined:

val any : ('a Deferred.t) list -> 'a Deferred.tIf

One use for `any` is to create functions that implement
timeout functionality. In certain situations, it is desirable to
execute a computation with a fixed time budget. This is easy with a
helper function `after` that builds deferred computations which
becomes determined at a fixed time in the future and the `any`
function just discussed. The `after` function has the following
signature:

val after : Core.Span.t -> unit Deferred.tThe type

val sec : float -> Core.Span.tHence, the expression

after (sec 5.3)is a deferred computation that will become determined after approximately 5.3 seconds (subject to the imprecision induced by the scheduler). Using this, we can write a higher-order function that executes another function

let timeout (thunk:unit -> 'a Deferred.t) (n:float) : ('a option) Deferred.t = Deferred.any [ after (sec n) >>| (fun () -> None) ; thunk () >>= (fun x -> Some x) ]The expression

Many patterns of coordination and synchronization can be
implemented using simple functions like `both`, `all`,
and `any`. But in other situations, more intricate behavior is
needed. An *ivar* can be used to implement fine-grained
coordination and synchronization between deferred values. Intuitively,
an ivar is like a reference that can be assigned to or *filled*
exactly once. In this way, an ivar is similar to a deferred
computation, which is intially undetermined and then becomes
determined exactly once.

The ivars interface is captured by the following code:

module IVar : sig type 'a t val create : unit -> 'a t val fill : 'a t -> 'a -> unit val fill_if_empty : 'a t -> 'a -> unit val is_empty : 'a t -> bool val is_full : 'a t -> bool val read : 'a t -> 'a Deferred.t ... end = struct ... endConsult the Ivar documentation for further details.

The key thing to note about the `Ivar` module is
the `read` function, which constructs a deferred value that
becomes determined when the ivar is filled. We will use this
functionality in the next example.

To illustrate the use of ivars, we will implement a simple
scheduler that delays jobs by a fixed amount of time. We will need the function `upon`,

upon : 'a Deferred.t -> ('a -> unit) -> unitwhich waits for a value in the first argument to become determined, and then executes the code in the (possibly side-effecting) second argument.

The interface for the delaying scheduler is as follows:

module type DELAYER = sig type t val create : float -> t val schedule : t -> (unit -> 'a Deferred.t) -> 'a Deferred.t endThe

The implementation for the delaying scheduler is as follows:

module Delayer : DELAYER = struct type t = { delay: Time.Span.t; jobs: (unit -> unit) Queue.t } let create delay = { sec delay; jobs = Queue.create () } let schedule t thunk = let ivar = Ivar.create () in Queue.enqueue t.jobs (fun () -> upon (thunk ()) (fun x -> Ivar.fill ivar x)); upon (after t.delay) (fun () -> (Queue.dequeue_exn t.jobs) ()); Ivar.read ivar endNote that the job is enqueued immediately and dequeued after the delay has elapsed. Also note how

The `any` function provides a way to choose just one of
several deferred computations. But if those deferred computations have
side effects, such as opening files or socket connections, printing to
the console, etc. then the effect may not be what is desired. This can
lead to problems in general if these side effects are not correctly
handled. However, OCaml does not provide general mechanisms to cleanly
revert or shutdown a computation that has been discarded.

To address this problem, Async provides the notion of a *choice*:

type Deferred.choiceTo construct a value of type choice, one provides a deferred computation as well a function:

val choice : 'a Deferred.t -> ('a -> 'b) -> 'b Deferred.choiceIntuitively a choice can be thought of as representing a computation where the function is executed if the deferred computation becomes determined and the choice is chosen. By placing all side-effecting pieces of the computation in the function, programmers can obtain finer-grained control if and when computations are discarded.

The function `choose` selects a list of choices:

val choose : ('a Deferred.choice) list -> 'a Deferred.tJust like