chp-2.2.0: An implementation of concurrency ideas from Communicating Sequential Processes



A module containing constructs for choice, or alting. An ALT (a term inherited from occam) is a choice between several alternate events. Events that inherently support choice are:

  • skip
  • Control.Concurrent.CHP.Monad.stop
  • Control.Concurrent.CHP.Monad.waitFor
  • Reading from a channel (including extended reads): that is, calls to Control.Concurrent.CHP.Channels.Communication.readChannel and Control.Concurrent.CHP.Channels.Communication.extReadChannel
  • Writing to a channel (including extended writes): that is, calls to Control.Concurrent.CHP.Channels.Communication.writeChannel and Control.Concurrent.CHP.Channels.Communication.extWriteChannel
  • Synchronising on a barrier (using Control.Concurrent.CHP.Barriers.syncBarrier)
  • An alting construct (that is, you can nest alts) such as alt, priAlt (or the operator versions)
  • A sequential composition, if the first event supports alting (i.e. is in this list)
  • A call to every, which joins together several items (see the documentation on every).

There are several other events that can occur in CHP; these are assumed to be always-ready when they are included in a choice. Examples include:

  • Enrolling and resigning with a barrier
  • Poisoning a channel
  • Processes composed in parallel (using runParallel, etc)
  • Any lifted IO event
  • Creating channels, barriers, etc
  • Claiming a shared channel (yet...)
  • A return statement by itself

Here are some examples of using alting:

  • Wait for an integer channel, or 1 second to elapse:
 liftM Just (readChannel c) <|> (waitFor 1000000 >> return Nothing)
  • Check if a channel is ready, otherwise return immediately; this can be done using the optional function from Control.Applicative:
 optional (readChannel c)
  • Wait for input from one of two (identically typed) channels
 readChannel c0 <|> readChannel c1
  • Check if a channel is ready; if so send, it on, otherwise return immediately:
 (readChannel c >>= writeChannel d) <|> skip

Note that if you wait for a sequential composition:

 (readChannel c >>= writeChannel d) <|> (writeChannel e 6 >> readChannel f)

This only waits for the first action in both (reading from channel c, or writing to channel e), not for all of the actions (as, for example, an STM transaction would).



alt :: [CHP a] -> CHP aSource

An alt between several actions, with arbitrary priority. The first available action is chosen (with an arbitrary choice if many actions are available at the same time), its body run, and its value returned.

(<->) :: CHP a -> CHP a -> CHP aSource

A useful operator to perform an alt. This operator is associative, and has arbitrary priority. When you have lots of guards, it is probably easier to use the alt function. alt may be more efficent than foldl1 (<->)

priAlt :: [CHP a] -> CHP aSource

An alt between several actions, with descending priority. The first available action is chosen (biased towards actions nearest the beginning of the list), its body run, and its value returned.

What priority means here is a difficult thing, and in some ways a historical artifact. We can group the guards into three categories:

  1. synchronisation guards (reading from and writing to channels, and synchronising on barriers)
  2. time-out guards (such as Control.Concurrent.CHP.Monad.waitFor)
  3. dummy guards (Control.Concurrent.CHP.Monad.skip, Control.Concurrent.CHP.Monad.stop and things like IO actions)

There exists priority when comparing dummy guards to anything else. So for example,

 priAlt [ skip, x ]

Will always select the first guard (the skip guard), whereas:

 priAlt [ x , skip ]

Is an effective way to poll and see if x is ready, otherwise the Control.Concurrent.CHP.Monad.skip will be chosen. However, there is no priority between synchronisation guards and time-out guards. So the two lines:

 priAlt [ x, y ]
 priAlt [ y, x ]

May have the same or different behaviour (when x and y are not dummy guards), there is no guarantee either way. The reason behind this is that if you ask for:

 priAlt [ readChannel c, writeChannel d 6 ]

And the process at the other end is asking for:

 priAlt [ readChannel d, writeChannel c 8 ]

Whichever channel is chosen by both processes will not satisfy the priority at one end (if such priority between channels was supported). If you do want priority that is globally consistent, look at the channel and barrier creation methods for ways to set priority on events.

(</>) :: CHP a -> CHP a -> CHP aSource

A useful operator to perform a priAlt. This operator is associative, and has descending priority (that is, it is left-biased). When you have lots of actions, it is probably easier to use the priAlt function. priAlt may be more efficent than foldl1 (</>)

Since version 2.2.0, this operator is deprecated in favour of the (|) operator from the Alternative type-class.

every :: [CHP a] -> CHP [a]Source

Runs all the given processes in parallel with each other, but only when the choice at the beginning of each item is ready.

So for example, if you do:

 every [ readChannel c >>= writeChannel d, readChannel e >>= writeChannel f]

This will forward values from c and e to d and f respectively in parallel, but only once both channels c and e are ready to be read from. So f cannot be written to before c is read from (contrast this with what would happen if every were replaced with runParallel).

This behaviour can be somewhat useful, but every is much more powerful when used as part of an alt. This code:

 alt [ every [ readChannel c, readChannel d]
     , every [ writeChannel e 6, writeChannel f 8] ]

Waits to either read from channels c and d, or to write to channels e and f.

The events involved can partially overlap, e.g.

 alt [ every [ readChannel a, readChannel b]
     , every [ readChannel a, writeChannel c 6] ]

This will wait to either read from channels a and b, or to read from a and write to c, whichever combination is ready first. If both are ready, the choice between them will be arbitrary (just as with any other choices; see alt for more details).

The sets can even be subsets of each other, such as:

 alt [ every [ readChannel a, readChannel b]
     , every [ readChannel a, readChannel b, readChannel b] ]

In this case there are no guarantees as to which choice will happen. Do not assume it will be the smaller, and do not assume it will be the larger.

Be wary of what happens if a single event is included multiple times in the same every, as this may not do what you expect (with or without choice). Consider:

 every [ readChannel c >> writeChannel d 6
       , readChannel c >> writeChannel d 8 ]

What will happen is that the excecution will wait to read from c, but then it will execute only one of the bodies (an arbitrary choice). In general, do not rely on this behaviour, and instead try to avoid having the same events in an every. Also note that if you synchronise on a barrier twice in an every, this will only count as one member enrolling, even if you have two enrolled ends! For such a use, look at runParallel instead.

Also note that this currently applies to both ends of channels, so that:

 every [ readChannel c, writeChannel c 2 ]

Will block indefinitely, rather than completing the communication.

Each item every must support choice (and in fact only a subset of the items supported by alt are supported by every). Currently the items in the list passed to every must be one of the following:

  • A call to Control.Concurrent.CHP.Channels.readChannel (or Control.Concurrent.CHP.Channels.extReadChannel).
  • A call to Control.Concurrent.CHP.Channels.writeChannel (or Control.Concurrent.CHP.Channels.extWriteChannel).
  • skip, the always-ready guard.
  • Control.Concurrent.CHP.Monad.stop, the never-ready guard (will cause the whole every to never be ready, since every has to wait for all guards).
  • A call to Control.Concurrent.CHP.Monad.syncBarrier.
  • A sequential composition where the first item is one of the things in this list.
  • A call to every (you can nest every blocks inside each other).

Timeouts (e.g. Control.Concurrent.CHP.Monad.waitFor) are currently not supported. You can always get another process to synchronise on an event with you once a certain time has passed.

Note also that you cannot put an alt inside an every. So you cannot say:

 every [ readChannel c
       , alt [ readChannel d, readChannel e ] ]

To wait for c and (d or e) like this you must expand it out into (c and d) or (c and e):

 alt [ every [ readChannel c, readChannel d]
     , every [ readChannel c, readChannel e] ]

As long as x meets the conditions laid out above, every [x] will have the same behaviour as x.

Added in version 1.1.0

every_ :: [CHP a] -> CHP ()Source

Like every but discards the results.

Added in version 1.8.0.

(<&>) :: CHP a -> CHP b -> CHP (a, b)Source

A useful operator that acts like every. The operator is associative and commutative (see every for notes on idempotence). When you have lots of things to join with this operator, it's probably easier to use the every function.

Added in version 1.1.0