present iterators (type
enum) over the elements (type
of a collection (type
start builds an enumerator from a structure of type
step returns the first value built from an
enumerator and the enumerator that builds the next values. We
illustrate the idea of iterators by an enumerator over
an integer interval.
We then define a channel
par_iter that applies
worker to all the values produced by an enumerator,
function calls being performed concurrently.
step enum does not return a value,
then there is nothing to do (
0 is the empty process).
Otherwise, a recursive sending to
par_iter and a call to
worker are performed concurrently.
worker x ; 0 lifts
worker x (of type
unit) into a process.
An example of
par_iter usage is to print the integers
from 1 to 3 in unspecified order:
A possible output is:
To share parallel computations between several “agents”, we introduce the notion of a pool of functions.
pool exports two channels:
Viewed from caller side,
register is for offering computational
compute is for exploiting computational power.
The pool implementation matches
computing agents and exploiting agents with
a straightforward join-pattern.
Internally, available computing agents are messages pending on
Notice that several instances of a given
agent cannot execute concurrently.
Namely, a computation can start only when
an agent is available, and an agent engaged in a computation
returns to available status only when the computation (
To illustrate the use of the pool, we introduce a second function to print integers:
Then, we create a pool and register the
Finally, by combining
par_iter and the
of the pool, we have the integer interval printed by two agents:
A possible output is
Due to concurrency of distinct agents,
another possible output is
In the previous example,
par_iter is an asynchronous
channel. This clearly expresses that it cannot be known
par_iter has finished its work.
We now wish not to perform side effects, as
More precisely, we aim at building a new kind of
pool, with a “fold” function that
par_iter as regards concurrency, but additionally
returns a combination of results.
Interface for exploiting the pool is the
fold function that,
with respect to the previous
takes the combination function and an initial value for the result as extra
arguments, and returns a combined result of type
offering computational power is
register as before,
but now registered functions return a result of type
The new pool is controlled by the following monitor, of which primary job is to collect results.
A monitor provides four channels:
is used by the monitored pool (see below) to
signal that a new task starts (resp. ends).
The monitored pool will send a message on
iteration has come to an end.
wait is a function that returns
the combination of all the results of the monitored tasks.
A monitor has an internal state of which
n counts the number of tasks being computed.
This counter is updated with the channels
Additionally, the message on
leave is a task result to be
combined with the second component
The last join-pattern (
state(0,r) & finished()
& wait()) states
that when there are no more tasks, either active (
or to be allocated (
finished()), then the call to
We now present the pool implementation:
Let us examine the definition of
it first creates a monitor, then starts the iteration,
and finally calls the
wait function of the monitor.
definition is essentially a combination of the previous pool
implementation and of
par_iter, with worker calls being put
aside for clarity (channel
The combination has the effect that
various instances of a given
now execute in sequence, following iteration order.
Additionally, calls to
the monitor are inserted at appropriate places.
A remarkable point is that we can be sure that all
monitor.enter have been performed before
the message on
monitor.finished is sent.
This is almost obvious by considering that the recursive sending
loop is performed only once the call
monitor.enter() has returned,
by the virtue of the sequencing operator “
Moreover, the internal counter of the monitor
indeed counts active tasks:
&” binds more tightly than
;”, the process
returned, and thus once the monitor counter has been incremented.
Similarly, the counter is decremented (by
once the worker has returned.
The new pool is quite powerful, since it can serve as a meeting place between several agents that offer computational power and several agents that exploit it. Let us define the former agents and register them.
Exploiting agents are two functions, with different combination behavior.
Finally all agents meet through the pool.
A possible output is
In JoCaml, concurrent and distributed computations are based on the same model: different programs (abstracted as sites) may communicate by the means of channels. More precisely, site A may send messages on a channel of which definition resides on another site B. In that situation, guarded processes execute on site B. One practical problem in distributed applications is for the communicating partners, first to know one another, and then to have at least a few channels in common. To solve the issue, JoCaml provides library calls to connect sites and a name service, which basically is a repository for values (more specifically channel names) indexed by plain strings.
As an example, this first program runs on machine A.
This program creates a pool. Then, by calling
Join.Ns.register, it stores
pool.register associated to the
"reg" in the local name service
Join.Site.listen starts to listen for
connections on the default Internet address of the local site
Join.Site.get_local_addr()) on port
Finally, the program calls
This call blocks, since no computing agent has entered the pool yet.
To become such a computing agent, machine B runs the following program.
Here, B first
gets the site identity of the program running on A,
with the function
Join.Site.there, and its name service with
Then, it retrieves the (synchronous) channel associated
to the key
The name service is not type safe3.
For instance, the type of
string -> 'a. As a minimal precaution,
we insert explicit type constraints.
Finally, B defines and registers the synchronous
The effect of A calling the registered
double is the
one of a remote function call.
Hence, console output is
12 on A
(1)(2)(3) on B.
Another issue deserves mention.
The program of B above is not complete:
spawn register(double) returns immediately,
execution goes on.
For the program not to terminate by reaching its end,
we deadlock it purposely.
But B is now blocked for ever, whereas a desirable behavior is for B to be released when A does not need B anymore, or at least when the program running on A terminates.
Join.Site.at_fail provides a convenient solution.
It takes a site A and a channel
unit Join.chan) as arguments,
When it is detected that A has failed, then a message
is sent on the channel. Thus, we replace the code above by: