我对Threepenny-Gui与StateT的互动有疑问。 考虑这个玩具程序,每次点击按钮时,在列表中添加一个“Hi”项目:
import Control.Monad
import Control.Monad.State
import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core hiding (get)
main :: IO ()
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup w = void $ do
return w # set title "Ciao"
buttonAndList <- mkButtonAndList
getBody w #+ map element buttonAndList
mkButtonAndList :: UI [Element]
mkButtonAndList = do
myButton <- UI.button # set text "Click me!"
myList <- UI.ul
on UI.click myButton $ \_ -> element myList #+ [UI.li # set text "Hi"]
return [myButton, myList]
现在,我希望打印自然数字,而不是“嗨”。我知道我可以使用UI monad是IO包装器的事实,并且读取/写入我在数据库中到目前为止所达到的数字,但出于教育目的,我想知道我是否可以使用它StateT,或通过Threepenny-gui界面访问列表的内容。
答案 0 :(得分:4)
StateT
在这种情况下不起作用。问题是你需要计数器的状态在按钮回调的调用之间保持不变。由于回调(以及startGUI
也会产生UI
操作,因此使用它们运行的任何StateT
计算都必须是自包含的,因此您可以调用runStateT
并使用生成的UI
操作。
使用Threepenny保持持久状态有两种主要方法。第一个也是最直接的是使用IORef
(它只是一个存在于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]
第二种方法是从命令式回调接口切换到Reactive.Threepenny
提供的声明式FRP接口。
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
的典型用法如下:
Graphics.UI.Threepenny.Events
(或domEvent
)从用户输入获得Event
。此处,“原始”输入事件为eClick
。Control.Applicative
和Reactive.Threepenny
组合器按摩事件数据。在我们的示例中,我们将eClick
转发为eIncrement
和eCount
,在每种情况下设置不同的事件数据。 Behavior
(如bCounter
)或回调(使用onEvent
)来利用事件数据。行为有点像一个可变变量,除了对它的更改是由您的事件网络以原则方式指定的,而不是通过代码库散布的任意更新。处理此处未显示的行为的有用函数是sink
函数,它允许您将DOM中的属性绑定到行为的值。另外一个例子,加上对这两种方法的更多评论,在this question和Apfelmus'答案中提供。
细节:在FRP版本中您可能会关注的一件事是eCount
是否会在bCounter
触发更新之前或之后获得eIncrement
中的值。答案是,正如预期的那样,该值肯定会是旧值,因为正如Reactive.Threepenny
文档所述,Behavior
更新和回调触发具有其他{{{ 1}}操纵。