It's known how to make a pure concurrency monad based on ContT, based on a functional pearl of Koen Claessen:
data Action m where
Atom :: m (Action m) -> Action m
Fork :: [Action m] -> Action m
Stop :: Action m
fork :: Applicative m => [ContT (Action m) m a] -> ContT (Action m) m ()
fork processes = ContT $ \next -> Fork <$> sequenceA (next () : [ process $ const $ pure $ Const | ContT process <- processes ])
How would I implement shared variables like IORefs or MVars? Or at least an async/await mechanism?
Bonus points if it's polymorphic in the type of data passed.
I assume by “implement shared variables like
IORefsorMVars” you mean in a way other than just having the underlying monadmincludeIOand usingIORef/MVar. That’s straightforward, something like this:A conventional way to add mutable variables to the “poor man’s concurrency monad” purely is by adding additional actions to the
Actiontype for creating, reading, and writing mutable variables. Suppose we have some typeVar m athat identifies mutable variables of typeathat can be created & accessed inm.Notice that the type parameter
adoes not appear in the result type of these new constructors, so it’s existentially quantified, and variables may thus contain values of any type.Newis an action that continues to another action with a fresh variable as an argument;Read, given a variable, continues to the next action with that variable’s value; andWrite, given a variable and a new value, writes the value into the variable before continuing.Like
fork, these would be constructed with helper functions that produce actions inContT (Action m) m:After that, you just have to decide on a suitable representation of
Var. One method is a data family, which makes it relatively easy to useIORef/MVarwhenIOis available, and something else like anIntindex into anIntMapotherwise.Of course this is just a sketch; a much more fleshed out implementation can be found in the
monad-parpackage, whose design is described in A Monad for Deterministic Parallelism (Marlow, Newton, & Peyton Jones 2011); itsParmonad is basically a continuation monad around an action type like this, and itsIVarabstraction is implemented similarly to this, with some additional constraints like extra strictness to enforce determinism and allow pure execution of internally impure code (anIVarsecretly wraps anIORef).