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
jsIntervalis just a plain IO action. If you implement that action usingrunStateTyou 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 tojsIntervalis 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 theIORefin the enclosing action and let the callback modify it. You can still write the callback inStateTstyle if you wish - just extract the state from theIORefand pass it torunStateT. Your code doesn't do this, you are just referencing the parametersfrom the top-level : you need to use the IORef, something like this:You can't really use
Maybeunless you are prepared to teach the actionmhow to cope with aMaybe- it need to deal with theNothing, so perhaps you want it to have the typeStateT (Maybe s) IO ()?A second logic problem (?) with your code is that certainly the
sreturned byintervalwill 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.