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



A module containing the ALT constructs. An ALT (a term inherited from occam) is a choice between several alternate events. In CHP, we say that an event must support alting to be a valid choice. Events that do support alting are:

  • skip
  • stop
  • waitFor
  • Reading from a channel (including extended reads): that is, calls to Control.Concurrent.CHP.Channels.readChannel and Control.Concurrent.CHP.Channels.extReadChannel
  • Writing to a channel (including extended writes): that is, calls to Control.Concurrent.CHP.Channels.writeChannel and Control.Concurrent.CHP.Channels.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).

Examples of events that do NOT support alting are:

  • 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...)

It is not easily possible to represent this at the type level (while still making CHP easy to use). Therefore it is left to you to not try to alt over something that does not support it. Given how much of the library does support alting, that should hopefully be straightforward.

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. Note that you must use the alt operator with priority here, otherwise your skip guard might be chosen, even when the channel is ready.
 liftM Just (readChannel c) </> (skip >> return Nothing)
  • 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 waitFor)
  3. dummy guards (skip and stop)

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

 priAlt [ skip, x ]

Will always select the first guard, whereas:

 priAlt [ x , skip ]

Is an effective way to poll and see if x is ready, otherwise the 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).

(</>) :: 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 (</>)

every :: forall a. [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.
  • 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. 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

(<&>) :: forall a b. 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