haskell - 混合 Threepenny-Gui 和 StateT

标签 haskell monad-transformers state-monad threepenny-gui

我有一个关于 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并利用结果 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]

第二种方式是从命令式回调接口(interface)切换到 Reactive.Threepenny 提供的声明式FRP接口(interface).

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 (或 domEvent ,如果您选择的事件未包含在该模块中)。这里,“原始”输入事件是 eClick .
  • 然后,您可以使用 Control.Applicative 来处理事件数据。和Reactive.Threepenny组合器。在我们的示例中,我们转发 eClickeIncrementeCount ,在每种情况下设置不同的事件数据。
  • 最后,您可以通过构建 Behavior 来利用事件数据。 (如 bCounter )或回调(通过使用 onEvent )。行为有点像可变变量,只不过对它的更改是由事件网络以原则性的方式指定的,而不是通过散布在代码库中的任意更新来指定的。用于处理此处未显示的行为的有用函数是 sink 函数,它允许您将 DOM 中的属性绑定(bind)到行为的值。

this question 中提供了一个额外的示例,以及对这两种方法的更多评论。以及 Apfelmus 的回答。


细节:在 FRP 版本中您可能关心的一件事是是否 eCount将得到 bCounter 中的值eIncrement 触发更新之前或之后。答案是,该值肯定会是旧值,正如预期的那样,因为,正如 Reactive.Threepenny 中提到的那样。文档,Behavior更新和回调触发具有名义上的延迟,而其他Event则不会发生这种情况。操纵。

关于haskell - 混合 Threepenny-Gui 和 StateT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24117788/

相关文章:

haskell - 从(操作 a)的内容动态生成规则

haskell - 隐藏构造函数

Scala:从猫中创建自定义 OptionT monad 进行学习

haskell - 如何向此 monad 转换器添加列表或 List?

haskell - 在 Haskell 中包含随机和列表的状态单子(monad)

haskell - 为什么这种类型注释会让我的功能依赖冲突消失? (为什么它只发生在某些版本的 GHC 上?)

haskell - Haskell 中有自定义预处理器的示例吗?

haskell - MaybeT 的 m 在类型签名中

haskell - 理解 Reader monad

Haskell - Case Of "Couldn' t 匹配预期类型时的模式匹配”