Strange behavior of accumE with Event (UI a -> UI a)

149 views Asked by At

I'm experimenting with threepenny-gui, trying to learn the FRP interface. I want to avoid all explicit shared state using accumE/accumB, instead of IORef:s. I have four different signals (start, stop, timer, reset) which all affect a global state and the user interface. I'm using accumE w $ concatenate <$> unions [e0, e1, e2, e3] to make the events share the same state w. Here is a short snippet which captures the essence of it (with only one signal):

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 }

This seems to work fine (although I wonder if it is sane). However, if I instead change the event to have type Event (UI World -> UI World) (and remove the runW field), things go haywire:

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'

It seems that all UI actions somehow get accumulated, and executed an increasing number of times with each event! I thought accumE was like a fold, where the accumulating state is replaced unless explicitly accumulated. What is the proper/best way of dealing with this problem in general?

0

There are 0 answers