Control.Concurrent.CHP.Alt
Description
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
andControl.Concurrent.CHP.Channels.extReadChannel
- Writing to a channel (including extended writes): that is, calls to
Control.Concurrent.CHP.Channels.writeChannel
andControl.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 onevery
).
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).
Documentation
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.
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:
- synchronisation guards (reading from and writing to channels, and synchronising on barriers)
- time-out guards (such as
waitFor
) - dummy guards (
skip
andstop
)
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).
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
(orControl.Concurrent.CHP.Channels.extReadChannel
). - A call to
Control.Concurrent.CHP.Channels.writeChannel
(orControl.Concurrent.CHP.Channels.extWriteChannel
). -
skip
, the always-ready guard. -
stop
, the never-ready guard (will cause the wholeevery
to never be ready, sinceevery
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 nestevery
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