在 Haste 中为 StateT monad 编写间隔函数

Write interval function for the StateT monad in Haste

所以我自己编写了 StateT 的实现,因为我无法让转换器在 Haste 中正确编译。我想让 javascript setInterval 在我的状态 monad 中工作。这是对 setInterval.

的 ffi 调用
jsInterval :: Int -> IO () -> IO Int
jsInterval = ffi "(function(t,f){window.setInterval(f,t);})"

无论如何我都想不出在 m 传递给 jsInterval 之后得到它的结果。所以我尝试使用 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')

这没有用,因为它保持了原始状态。读取发生在写入之前。所以我写了一个函数,它会循环轮询,直到 IORef 被写入,但是它永远挂起。

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'

这个功能可以实现吗?我尝试为 StateT 编写 MonadEvent 的实例,但也没有成功。

您传递给 FFI 的 IO 操作 jsInterval 只是一个普通的 IO 操作。如果您使用 runStateT 实施该操作,您只是 运行 一点点 'local' StateT。它与封闭代码无关。

这是回调和 monad 堆栈的一般问题 - 回调(在 IO( 的意义上)jsInterval 的参数是一个回调)在它们的定义中选择了一个固定的 monad,它们无法推广到您可能在其他地方使用的其他单子效果。

因为回调 - 通常 - 可以随时调用,包括一次多次调用,在不同的线程中,在调用函数完成并且它的状态被销毁之后 - 你可以看到这是一个很难解决的问题一般解决。

实用的答案是,正如您所尝试的那样,只使用 IORef;在封闭操作中创建 IORef 并让回调对其进行修改。如果愿意,您仍然可以使用 StateT 样式编写回调 - 只需从 IORef 中提取状态并将其传递给 runStateT。您的代码不会这样做,您只是从顶层引用参数 s:您需要使用 IORef,如下所示:

id_ <- jsInterval i $ do
                   current_s <- readIORef ref
                   (_, new_s) <- runStateT m current_s
                   writeIORef ref (new_s)

你不能真正使用 Maybe 除非你准备教动作 m 如何处理 Maybe - 它需要处理 Nothing, 所以也许你希望它具有类型 StateT (Maybe s) IO () ?

您的代码的第二个逻辑问题 (?) 肯定是 interval 返回的 s 还没有更改 - setInterval 代码不可能被触发,直到 javascript 回到空闲循环。

传递回调的一般问题多年来已经讨论过几次,请参阅:

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

等等