[Haskell-cafe] Yet Another Forkable Class
John ExFalso
0slemi0 at gmail.com
Thu Aug 22 00:42:12 CEST 2013
TLDR: New forkable monad/transformer suggestion
http://pastebin.com/QNUVL12v(hpaste is down)
Hi,
There are a dozen packages on hackage defining a class for monads that can
be forked, however none of these are modular enough to be useful in my
opinion.
In particular the following are not addressed:
1. Cases when the child thread's monad is different from the parent's
2. Monad transformers (this is somewhat addressed with
Control.Monad.Trans.Control)
I will try to demonstrate both issues with an example.
1. WebSockets
WebSockets is a monad that cannot itself be forked. This is because at any
given time there should only be a single thread listening on a websocket.
However there is a reasonable monad that can be forked off, namely one that
can send to the websocket - one that has access to the Sink.
So first off a "Forkable" class should not look like this:
class (MonadIO m, MonadIO n) => Forkable m where
fork :: m () -> m ThreadId
But rather like this:
class Forkable m n where
fork :: n () -> m ThreadId
For our example the instance would be
instance (Protocol p) => Forkable (WebSockets p) (ReaderT (Sink p) IO) where
fork (ReaderT f) = liftIO . forkIO . f =<< getSink
Another example would be a child that should not be able to throw errors as
opposed to the parent thread.
2. ReaderT
Continuing from the previous example to demonstrate the need to distinguish
forkable transformers.
Say we have some shared state S that both parent and child should have
access to:
type Parent p = ReaderT (TVar S) (WebSockets p)
type Child p = ReaderT (TVar S) (ReaderT (Sink p) IO)
The "forkability" of Child from Parent should be implied, however with
Forkable we have to write a separate instance.
So what I suggest is a second class:
class ForkableT t where
forkT :: (Forkable m n) => t n () -> t m ThreadId
And then:
instance ForkableT (ReaderT r) where
forkT (ReaderT f) = ReaderT $ fork . f
We can also introduce a default for Forkable that uses a ForkableT instance:
class (MonadIO m, MonadIO n) => Forkable m n where
fork :: n () -> m ThreadId
default fork :: ForkableT t => t n () -> t m ThreadId
fork = forkT
instance (Forkable m n) => Forkable (ReaderT r m) (ReaderT r n)
This means Child is automatically Forkable from Parent, no need to write a
specific case for our specific monads (and if we newtype it we can use
-XGeneralizedNewtypeDeriving)
Note how MonadTransControl already solves the specific problem of lifting a
forking operation into ReaderT. However consider ResourceT from
Control.Monad.Resource: it is basically a ReaderT, however in order to
safely deallocate resources when sharing reference counting is needed. This
means a simple lift would not suffice.
We can nevertheless provide a default ForkableT based on MonadTransControl:
class ForkableT t where
forkT :: (Forkable m n) => t n () -> t m ThreadId
default forkT :: (MonadTransControl t, Forkable m n) => t n () -> t m
ThreadId
forkT t = liftWith $ \run -> fork $ run t >> return ()
Actually resourcet's reference counting resourceForkIO also nicely
demonstrates the first problem:
type Parent p = ResourceT (WebSockets p)
type Child p = ResourceT (ReaderT (Sink p) IO)
Note how we cannot use resourceForkIO without touching the underlying
monads.
What do you think? Is there already an established way of modular forking?
I wouldn't like to litter hackage with another unusable Forkable class:)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20130821/9aadc2c6/attachment.htm>
More information about the Haskell-Cafe
mailing list