3

我正在试验threepenny-gui,试图学习FRP接口。我想避免使用 accumE/accumB 而不是 IORef:s 的所有显式共享状态。我有四个不同的信号(启动、停止、定时器、重置),它们都会影响全局状态和用户界面。我accumE w $ concatenate <$> unions [e0, e1, e2, e3]用来使事件共享相同的状态w。这是一个简短的片段,它抓住了它的本质(只有一个信号):

data World = World { intW :: Int , runW :: UI () }

main :: IO ()
main = startGUI defaultConfig setup

setup :: Window -> UI ()
setup _ = do
    (e0, fire) <- liftIO  UI.newEvent
    let e0' = action <$ e0
    e <- accumE (World 0 (return ())) e0'
    onEvent e $ \w -> void $ runW w
    replicateM_ 5 . liftIO $ fire ()
    where
        action :: World -> World
        action w = w { intW = succ $ intW w
                     , runW = liftIO . print $ intW w }

这似乎工作正常(尽管我想知道它是否正常)。但是,如果我改为将事件更改为具有类型Event (UI World -> UI World)(并删除 runW 字段),事情就会变得混乱:

data World = World { intW :: Int } deriving (Show)

main :: IO ()
main = startGUI defaultConfig setup

setup :: Window -> UI ()
setup _ = do
    (e0, fire) <- liftIO UI.newEvent
    let e0' = action <$ e0
    e <- accumE (return (World 0)) e0'
    onEvent e void
    replicateM_ 5 . liftIO $ fire ()
    where
        action :: UI World -> UI World
        action world = do
            w <- world
            let w' = w { intW = succ $ intW w }
            liftIO $ print w'
            return w'

似乎所有 UI 操作都以某种方式累积,并随着每个事件执行越来越多的次数!我认为 accumE 就像一个折叠,除非明确累积,否则累积状态会被替换。一般来说,处理这个问题的正确/最佳方法是什么?

4

0 回答 0