Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fork inside monad transformer

Consider some monad transformers stack, say

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
...
newtype J = J { runJ :: ErrorT Foo (StateT Bar IO) a } deriving (Applicative, Functor, etc)

And some functions in J:

peekNextQuux :: J Quux
peekNextQuux = ...

withJ :: J a -> IO (Either Foo a)
withJ = ...

Then I found myself inside J context. I can write

f = withJ $ peekNextQuux >>= liftIO . print

Now I want to peek and print quuxes inside separate thread inside J context

g = withJ . liftIO . forkIO . forever $ peekNextQuux >>= liftIO . print

Which obviously won't work. I guess there is some way to solve such a simple problem, just can't figure it out.

like image 332
Matvey Aksenov Avatar asked Mar 06 '12 13:03

Matvey Aksenov


2 Answers

I'm not sure if this is what you need, but it sounds like you are looking for a function

forkJ :: J () -> J ThreadId

which is similar to forkIO, but works in J context instead. Generally speaking all of dflemstr's points are valid. There are many unresolved questions about state management due to Haskell's purity.

However, if you're willing to restructure your logic a little bit, one option that may work for you (if all you're looking for is a separate thread with access to the original state when you issued the fork) is the lifted-base pakcage, which depends on monad-control. It will essentially give you the forkJ function above as long as you have IO at the bottom of your transformer stack.

Now, if you want the 2 threads to communicate in a stateful manner, such that errors raised in the child are propagated to the main thread as part of the ErrorT machinery, this is just not possible (as dflemstr explained). You can, however, establish a channel of communication between the 2 threads using a construct from the Control.Concurrent module family. One of the following modules may have what you need:

Control.Concurrent.Chan
Control.Concurrent.MVar
Control.Concurrent.STM
like image 154
ozataman Avatar answered Nov 05 '22 12:11

ozataman


How do you expect it to work? The separate thread has to have access to some state and some error handling, because J wraps StateT and ErrorT. How should the thread get access to this? When the state is updated in the new thread, should it be changed in the old thread too? When the new thread throws an exception, should the old thread halt?

It cannot work, because StateT and ErrorT are pure monad transformers, so the behaviors I described are not possible to implement. You must explicitly pass the state to the new thread and run a new state monad there for it to work:

g = withJ . ... $ do
  state <- get
  liftIO . forkIO $ do
    flip execStateT state . forever $ peekNextQuux >>= liftIO . print
like image 45
dflemstr Avatar answered Nov 05 '22 11:11

dflemstr