Suppose I want to compose a bunch of reads in the way select(2) does, then it's very tempting to write something like forever $ race (forever $ read a) (forever $ read b).
However looking at the implementation this won't work correctly, since new threads are created and killed on every call to race. So when one action "wins", the other actions are killed, but they might have consumed their streams in the meantime.
It's possible to create an implementation that does work properly, although it has to compose with forever inside the action - something like:
where running (_1, _2, _3) <- foreverConcurrently reads would itself give further actions to be run, that behave as follows:
an action that gets any of the results, whichever arrives first, in order
an action that waits for any ongoing actions to finish, as well as all their results to be consumed ("graceful shutdown") by the caller calling (1)
an action that kills the ongoing threads, potentially dropping any unconsumed values, but can return immediately ("ungraceful shutdown")
The implementation ideally would avoid running the original actions, unless (1) has indicated a concrete demand for them.
Does that seem like something sensible for this library? I will have to write code that does this anyway in one of my projects, I can contribute it here for others too. Note that the existance of this function here might help other people avoid bugs in their code, since the incorrect-but-obvious version forever $ race (forever $ read a) (forever $ read b) at a first glance seems perfectly acceptable.
Suppose I want to compose a bunch of reads in the way
select(2)
does, then it's very tempting to write something likeforever $ race (forever $ read a) (forever $ read b)
.However looking at the implementation this won't work correctly, since new threads are created and killed on every call to
race
. So when one action "wins", the other actions are killed, but they might have consumed their streams in the meantime.It's possible to create an implementation that does work properly, although it has to compose with
forever
inside the action - something like:where running
(_1, _2, _3) <- foreverConcurrently reads
would itself give further actions to be run, that behave as follows:The implementation ideally would avoid running the original actions, unless (1) has indicated a concrete demand for them.
Does that seem like something sensible for this library? I will have to write code that does this anyway in one of my projects, I can contribute it here for others too. Note that the existance of this function here might help other people avoid bugs in their code, since the incorrect-but-obvious version
forever $ race (forever $ read a) (forever $ read b)
at a first glance seems perfectly acceptable.