haskellmonad-transformersiorefhaste

Write interval function for the StateT monad in Haste


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.


Solution

  • 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.