The Go language has a select
statement that can be used to poll multiple channels and carry out a particular action depending on which channel is non-empty first.
E.g.
select {
case a := <- chanA:
foo(a)
case b := <- chanB:
baz(b)
case c := <- chanC:
bar(c)
}
This will wait until either chanA
, chanB
or chanC
is non-empty, then if for instance chanB
is non-empty, it will read from chanB
and store the result in b
, then call baz(b)
. A default:
clause can also be added, meaning the select
statement will not wait on the channels and instead will do whatever the default
clause is if all the channels are empty.
What would be the best way to implement something like this for STM TChan
s in Haskell? It could be done naively by an if-else chain: checking if each chan isEmptyChan
, and if it's not empty then reading from it and calling the appropriate function, or else calling retry
if all of the channels are empty. I was wondering if there would be a more elegant/idiomatic way to do this?
Note that Go's select
statement can also include send statements in its cases, and will only complete a send statement if its channel is empty. It would be great if that functionality could be duplicated too, although I'm not sure whether there would be an elegant way to do so.
Only slightly related but something I just noticed and I'm not sure where to post it: there's a typo on the Control.Monad.STM page in the description for retry
:
"The implementation may block the thread until one of the TVars that it has read from has been udpated."
You can implement select
semantics (both for read and write) using orElse (note: it is specific to ghc.)
For example:
forever $ atomically $
writeTChan chan1 "hello" `orElse` writeTChan chan2 "world" `orElse` ...
The idea is that when one action retries (e.g. you are writing to chan, but it is full; or you are reading from chan, but it is empty), the second action is performed.
The default
statement is just a return ()
as the last action in the chain.
Add:
As @Dustin noted, go selects random branch for good reason. Probably the easiest solution is to shuffle actions on each iteration, it should be ok in most cases. Replicating go semantics properly (shuffle active branches only) is a bit harder. Probably manually inspecting isEmptyChan
for all branches is the way to go.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With