我有一个问题,关于三分炮与StateT的相互作用.考虑一下这个玩具程序,每当单击按钮时,在列表中添加一个“嗨”项:
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接口访问列表的内容。
发布于 2014-06-10 02:34:42
在这种情况下,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提供的声明式玻璃钢接口。
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,如果您所选择的事件未被该模块覆盖)从用户输入中获取该事件。在这里,“原始”输入事件是eClick。Control.Applicative和Reactive.Threepenny组合器对事件数据进行按摩。在我们的示例中,我们将eClick转发为eIncrement和eCount,在每种情况下设置不同的事件数据。Behavior (如bCounter)或回调(通过使用onEvent)来利用事件数据。行为有点像一个可变变量,只不过对它的更改是由事件网络以原则的方式指定的,而不是通过散布在代码库中的任意更新来指定的。处理此处未显示的行为的一个有用函数是sink函数,它允许您将DOM中的属性绑定到行为的值。在this question和Apfelmus对这两种方法的答复中提供了另一个例子,再加上对这两种方法的更多评论。
细节:在玻璃钢版本中,你可能关心的一件事是,eCount是否会在eIncrement触发的更新之前或之后在bCounter中得到值。答案是,这个值肯定是原来的值,因为正如Reactive.Threepenny文档所提到的,Behavior更新和回调触发有一个在其他Event操作中不会发生的名义延迟。
https://stackoverflow.com/questions/24117788
复制相似问题