混合Threepenny-Gui和StateT [英] Mixing Threepenny-Gui and StateT
问题描述
我对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]
现在,我希望它打印自然数而不是"Hi".我知道我可以利用UI monad是IO的包装器这一事实,并读取/写入到目前为止在数据库中达到的数字,但是出于教育的目的,我想知道是否可以使用StateT,或通过Threepenny-gui界面访问列表的内容.
StateT
在这种情况下不起作用.问题是您需要计数器的状态在按钮回调的两次调用之间保持不变.由于回调(以及startGUI
也)产生UI
动作,因此使用它们进行的任何StateT
计算都必须是独立的,以便您可以调用runStateT
并利用生成的
通过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]
第二种方法是从命令式回调接口切换到由 此问题和Apfelmus对它. 细节:在FRP版本中,您可能要担心的一件事是, I have a question on the interaction of Threepenny-Gui with StateT.
Consider this toy program that, every time the button is clicked, adds a "Hi" item in the list: Now, instead of "Hi", I'd like it to print the natural numbers. I know that I could use the fact that the UI monad is a wrapper around IO, and read/write the number I reached so far in a database, but, for educational purposes, I'd like to know if I can do it using StateT, or otherwise accessing the content of the list via Threepenny-gui interface. There are two main ways to keep persistent state with Threepenny. The first and most immediate is using an The second way is switching from the imperative callback interface to the declarative FRP interface provided by Typical usage of An additional example, plus some more commentary on the two approaches, is provided in this question and Apfelmus' answer to it. Minutiae: one thing you might be concerned about in the FRP version is whether 这篇关于混合Threepenny-Gui和StateT的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!Reactive.Threepenny
的典型用法如下:
domEvent
(如果该模块未涵盖您选择的事件).此处,原始"输入事件为eClick
.Control.Applicative
和Reactive.Threepenny
组合器按摩事件数据.在我们的示例中,我们将eClick
分别转发为eIncrement
和eCount
,并在每种情况下设置不同的事件数据. Behavior
(例如bCounter
)或回调(通过使用 sink
函数,它使您可以将DOM中的属性绑定到行为的值.
eCount
是在eIncrement
触发的更新之前还是之后获取bCounter
中的值.答案是,该值肯定会按预期使用旧值,因为如Reactive.Threepenny
文档所述,Behavior
更新和回调触发具有名义上的延迟,而其他Event
操作则不会发生这种延迟. /p>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]
StateT
won't work in this case. The problem is that you need the state of your counter to persist between invocations of the button callback. Since the callback (and startGUI
as well) produce UI
actions, any StateT
computation to be ran using them has to be self-contained, so that you can call runStateT
and make use of the resulting UI
action.IORef
(which is just a mutable variable which lives in IO
) to hold the counter state. That results in code much like that written with conventional event-callback GUI libraries.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
goes like this:
Event
from user input through Graphics.UI.Threepenny.Events
(or domEvent
, if your chosen event is not covered by that module). Here, the "raw" input event is eClick
.Control.Applicative
and Reactive.Threepenny
combinators. In our example, we forward eClick
as eIncrement
and eCount
, setting different event data in each case. Behavior
(like bCounter
) or a callback (by using onEvent
) out of it. A behavior is somewhat like a mutable variable, except that changes to it are specified in a principled way by your network of events, and not by arbitrary updates strewn through your code base. An useful function for handling behaviors not shown here is sink
function, which allows you to bind an attribute in the DOM to the value of a behavior.
eCount
will get the value in bCounter
before or after the update triggered by eIncrement
. The answer is that the value will surely be the old one, as intended, because, as mentioned by the Reactive.Threepenny
documentation, Behavior
updates and callback firing have a notional delay that does not happen with other Event
manipulation.