为什么这个Reflex代码导致Dynamics无限期地以相同的值发射?

时间:2015-06-17 08:14:06

标签: haskell ghcjs reflex

这个小程序的目的是显示三个按钮,第三个按钮的标签最初为" 0"然后成为最后点击按钮的索引。现在,按钮的数量和其他按钮的标签是不变的。

当我用ghcjs编译这个自包含文件并在浏览器中加载Main.jsexe / index.html时,我可以看到两个traceDyns在一个循环中触发,两者总是具有值0.据我所知,在单击按钮之前不会发生任何事情,因为_el_clicked会为系统的其余部分提供信息。

另请注意,我使用mapDyn (fst . head . Map.toList)来提取所选按钮的索引 - 我不确定这是否正确,但无论哪种方式我都不知道什么导致无限循环。

{-# LANGUAGE RecursiveDo #-}

module Main where

import Reflex
import Reflex.Dom

import qualified Data.Map as Map

dynButton
  :: MonadWidget t m
  => Dynamic t String
  -> m (Event t ())
dynButton s = do
  (e, _) <- el' "button" $ dynText s
  return $ _el_clicked e

-- widget that takes dynamic list of strings
-- and displays a button for each, returning
-- an event of chosen button's index
listChoiceWidget
  :: MonadWidget t m
  => Dynamic t [String]
  -> m (Event t Int)
listChoiceWidget choices = el "div" $ do
  asMap <- mapDyn (Map.fromList . zip [(0::Int)..]) choices
  evs <- listWithKey asMap (\_ s -> dynButton s)
  k <- mapDyn (fst . head . Map.toList) evs
  return $ updated (traceDyn "k" k)

options :: MonadWidget t m => Dynamic t Int -> m (Dynamic t [String])
options foo = do
  mapDyn (\x -> ["a", "b", show x]) foo

main :: IO ()
main = mainWidget $ el "div" $ do
  rec n <- listChoiceWidget o
      o <- options foo
      foo <- holdDyn 0 n
  display (traceDyn "foo" foo)

1 个答案:

答案 0 :(得分:5)

看起来你的listChoiceWidget代码丢弃了dynButton构建的点击事件。

listWithKey返回m (Dynamic t (Map k a))。在您的情况下,键的类型为Int,值为Event t ()(由dynButton生成)。

在这一行:

k <- mapDyn (fst . head . Map.toList) evs

您正在将Dynamic t (Map Int (Event t ()))转变为Dynamic t Int但是,至关重要的是,当点击事件触发时您不会这样做。此行映射到evs并生成一个Dynamic,它始终包含Int to Events Map中的第一个键,无论事件是否已触发。它始终是包含Int 0的动态。

您看到循环的原因是:

  1. mainfoo的初始值0引入options
  2. 构建新选项
  3. listChoiceWidget收到新选项并更新列表
  4. 生成的Ints to Events地图的第一个键已更新
  5. foo收到来自listChoiceWidget
  6. 的密钥更新事件
  7. 无限回到第2步
  8. 您需要某种方法来确定最后一次按钮点击事件,而不是从地图中检索第一个键。您的地图已包含显示的每个按钮的点击事件。现在这些事件的类型为Event t (),但你真正需要的是Event t Int,所以当事件触发时你可以告诉它来自哪个按钮。

    evs' <- mapDyn (Map.mapWithKey (\k e -> fmap (const k) e)) evs
    

    evs'的类型为Dynamic t (Map Int (Event t Int))。接下来,我们需要一些方法来组合我们的事件,以便我们使用最近点击的按钮键激活一个事件。

    dynEv <- mapDyn (leftmost . Map.elems . Map.mapWithKey (\k e -> fmap (const k) e)) evs
    

    dynEv现在的类型为Dynamic t (Event t Int)。地图的关键已经融入事件中,因此我们不再需要它们了。 Map.elems将我们的事件地图转换为事件列表,leftmost允许您将事件列表合并为一个事件。

    来自leftmost的{​​{3}}:&#34;如果列表中至少有一个事件发生,则创建一个新事件。如果多个同时出现,则使用给定的函数从左侧折叠。&#34;

    最后,我们需要将您的Dynamic t (Event t Int)转换为Event t Int。我们将使用switchBehavior t (Event t a)并返回Event t a。因此,以下行将生成Event t Int

    switch (current dynEv)
    

    current提取Behavior的{​​{1}},Dynamic创建&#34;当前选择的输入事件发生时将发生的事件。&# 34;

    这是修订后的switch代码。我已经包含了内联类型注释,因此您需要启用listChoiceWidget语言扩展来编译此代码(或者您可以删除注释)。

    ScopedTypeVariables

    docs