混合Threepenny-Gui和StateT [英] Mixing Threepenny-Gui and StateT

查看:88
本文介绍了混合Threepenny-Gui和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]

第二种方法是从命令式回调接口切换到由

Reactive.Threepenny的典型用法如下:

  • 首先,您可以通过eClick.
  • 然后,使用Control.ApplicativeReactive.Threepenny组合器按摩事件数据.在我们的示例中,我们将eClick分别转发为eIncrementeCount,并在每种情况下设置不同的事件数据.
  • 最后,通过构建Behavior(例如bCounter)或回调(通过使用此问题和Apfelmus对它.


    细节:在FRP版本中,您可能要担心的一件事是,eCount是在eIncrement触发的更新之前还是之后获取bCounter中的值.答案是,该值肯定会按预期使用旧值,因为如Reactive.Threepenny文档所述,Behavior更新和回调触发具有名义上的延迟,而其他Event操作则不会发生这种延迟. /p>

    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:

    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]
    

    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.

    解决方案

    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.

    There are two main ways to keep persistent state with Threepenny. The first and most immediate is using an 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]
    

    The second way is switching from the imperative callback interface to the declarative FRP interface provided by 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]
    

    Typical usage of Reactive.Threepenny goes like this:

    • First, you get hold of an 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.
    • Then, you massage event data using Control.Applicative and Reactive.Threepenny combinators. In our example, we forward eClick as eIncrement and eCount, setting different event data in each case.
    • Finally, you make use of the event data, by building either a 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.

    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 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.

    这篇关于混合Threepenny-Gui和StateT的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆