So I wrote my own implementation of StateT
because I couldn't get transformers to compile correctly in Haste. I think wanted to get the javascript setInterval
working inside my state monad. Here is the ffi call to setInterval
.
jsInterval :: Int -> IO () -> IO Int
jsInterval = ffi "(function(t,f){window.setInterval(f,t);})"
I couldn't think of anyway to get the result of m
back after it is passed to jsInterval
. So I tried to use IORefs
.
interval :: Int -> StateT s IO () -> StateT s IO Int
interval i m = StateT $ \s -> do
ref <- newIORef Nothing
id_ <- jsInterval i $ do
(_, s') <- runStateT m s
writeIORef ref (Just s')
s' <- readIORef ref
return (id_, s')
This didn't work because it kept the original state. The read happened before the write. So I wrote a function that would poll in a loop until the IORef
was written but this just hung forever.
interval :: Int -> StateT s IO () -> StateT s IO Int
interval i m = StateT $ \s -> do
ref <- newIORef Nothing
id_ <- jsInterval i $ do
(_, s') <- runStateT m s
writeIORef ref (Just s')
s' <- go ref
return (id_, s')
where
go ref = do
s <- readIORef ref
case s of
Nothing -> go ref
Just s' -> return s'
Is it possible to implement this function? I tried writing an instance of MonadEvent
for StateT
but that was also unsuccessful.
The IO action you are passing to your FFI'ed jsInterval
is just a plain IO action. If you implement that action using runStateT
you are just running a little 'local' StateT
. It's unrelated to the enclosing code.
This is a generic problem with callbacks and monad stacks - callbacks (in the sense that the IO(
) parameter to jsInterval
is a callback) have a fixed monad chosen in their definition and they have no way to generalise to other monadic effects you might be using elsewhere.
Since callbacks - in general - can be called at any time, including multiple times at once, in different threads, after the calling function has completed and its state has been destroyed - you can see that this is hard problem to solve in general.
The pragmatic answer is, as you have tried, to just use an IORef
; create the IORef
in the enclosing action and let the callback modify it. You can still write the callback in StateT
style if you wish - just extract the state from the IORef
and pass it to runStateT
. Your code doesn't do this, you are just referencing the parameter s
from the top-level : you need to use the IORef, something like this:
id_ <- jsInterval i $ do
current_s <- readIORef ref
(_, new_s) <- runStateT m current_s
writeIORef ref (new_s)
You can't really use Maybe
unless you are prepared to teach the action m
how to cope with a Maybe
- it need to deal with the Nothing
, so perhaps you want it to have the type StateT (Maybe s) IO ()
?
A second logic problem (?) with your code is that certainly the s
returned by interval
will not have been changed yet - the setInterval code can't possibly have been triggered until javascript goes back into its idle loop.
The general problem of passing callbacks has been discussed a few times over the years, see:
https://mail.haskell.org/pipermail/haskell-cafe/2007-July/028501.html http://andersk.mit.edu/haskell/monad-peel/ http://blog.sigfpe.com/2011/10/quick-and-dirty-reinversion-of-control.html
etc.