StateT
在这种情况下不起作用。问题是您需要在按钮回调调用之间保持计数器的状态。由于回调(并且startGUI
以及)生产UI
行动,任何StateT
使用它们运行的计算必须是独立的,以便您可以调用runStateT
并利用所得结果UI
action.
使用 Threepenny 有两种主要方法来保持持久状态。第一个也是最直接的方法是使用IORef http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-IORef.html(这只是一个可变变量,位于IO
) 保持计数器状态。这会产生与使用传统事件回调 GUI 库编写的代码非常相似的代码。
import Data.IORef
import Control.Monad.Trans (liftIO)
-- etc.
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
counter <- liftIO $ newIORef (0 :: Int) -- Mutable cell initialization.
on UI.click myButton $ \_ -> do
count <- liftIO $ readIORef counter -- Reads the current value.
element myList #+ [UI.li # set text (show count)]
lift IO $ modifyIORef counter (+1) -- Increments the counter.
return [myButton, myList]
第二种方式是从命令式回调接口切换到声明式 FRP 接口Reactive.Threepenny http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Reactive-Threepenny.html.
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
let eClick = UI.click myButton -- Event fired by button clicks.
eIncrement = (+1) <$ eClick -- The (+1) function is carried as event data.
bCounter <- accumB 0 eIncrement -- Accumulates the increments into a counter.
-- A separate event will carry the current value of the counter.
let eCount = bCounter <@ eClick
-- Registers a callback.
onEvent eCount $ \count ->
element myList #+ [UI.li # set text (show count)]
return [myButton, myList]
典型用法Reactive.Threepenny
是这样的:
- 首先,你要掌握一个
Event
从用户输入通过Graphics.UI.Threepenny.Events http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Graphics-UI-Threepenny-Events.html (or domEvent http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Graphics-UI-Threepenny-Core.html#v:domEvent,如果您选择的事件不包含在该模块中)。这里,“原始”输入事件是eClick
.
- 然后,您可以使用以下方式按摩事件数据
Control.Applicative
and Reactive.Threepenny
组合器。在我们的例子中,我们转发eClick
as eIncrement
and eCount
,在每种情况下设置不同的事件数据。
- 最后,您可以通过构建一个来使用事件数据
Behavior
(like bCounter
)或回调(通过使用onEvent http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Graphics-UI-Threepenny-Core.html#v:onEvent) 出来。行为有点像可变变量,只不过对它的更改是由事件网络以原则性的方式指定的,而不是通过散布在代码库中的任意更新来指定的。用于处理此处未显示的行为的有用函数是sink http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Graphics-UI-Threepenny-Core.html#v:sink函数,它允许您将 DOM 中的属性绑定到行为的值。
中提供了另一个示例以及对这两种方法的更多评论这个问题 https://stackoverflow.com/q/20872825/2751851以及阿普费尔姆斯对此的回答。
细节:在 FRP 版本中您可能关心的一件事是是否eCount
将得到的值bCounter
在更新触发之前或之后eIncrement
。答案是,该值肯定会是旧值,正如预期的那样,因为,正如所提到的Reactive.Threepenny
文档,Behavior
更新和回调触发有一个名义上的延迟,而其他延迟则不会发生Event
操纵。