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

ajp*_*ajp 5 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)
Run Code Online (Sandbox Code Playgroud)

小智 5

看起来你的listChoiceWidget代码丢弃了dynButton构造的click事件.

listWithKey回报m (Dynamic t (Map k a)).在您的情况下,键是类型Int和值Event t ()(由dynButton生成).

在这一行:

k <- mapDyn (fst . head . Map.toList) evs
Run Code Online (Sandbox Code Playgroud)

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

你看到一个循环的原因是因为:

  1. mainfoo其初始值为0的Feed 输入options
  2. 构建新选项
  3. listChoiceWidget 接收新选项并更新列表
  4. 生成的Ints to Events Map的第一个键已更新
  5. foo 从中接收密钥更新事件 listChoiceWidget
  6. 无限回到第2步

您无需从Map中检索第一个键,而是需要某种方法来确定最后一个按钮单击事件.您的地图已包含显示的每个按钮的点击事件.现在这些事件都有类型Event t (),但你真正需要的是Event t Int,所以当事件触发时你可以告诉它来自哪个按钮.

evs' <- mapDyn (Map.mapWithKey (\k e -> fmap (const k) e)) evs
Run Code Online (Sandbox Code Playgroud)

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

dynEv <- mapDyn (leftmost . Map.elems . Map.mapWithKey (\k e -> fmap (const k) e)) evs
Run Code Online (Sandbox Code Playgroud)

dynEv现在有了这种类型Dynamic t (Event t Int).地图的键已经被添​​加到事件中,因此我们不再需要它们了.Map.elems将我们的事件Map转换为事件列表,并leftmost允许您将事件列表组合到一个事件中.

文档leftmost:"创建,如果出现在列表中的事件的至少一个如果多发生在它们从左边给定函数折叠,同时出现一个新的事件."

最后,我们需要将您Dynamic t (Event t Int)转换为Event t Int.我们将要使用switch,它需要一个Behavior t (Event t a)并返回一个Event t a.因此,以下行将导致Event t Int.

switch (current dynEv)
Run Code Online (Sandbox Code Playgroud)

current提取Behaviora的a Dynamic,并switch创建"当前选择的输入事件发生时将发生的事件".

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

listChoiceWidget
  :: forall t m. MonadWidget t m
  => Dynamic t [String]
  -> m (Event t Int)
listChoiceWidget choices = el "div" $ do
  asMap <- mapDyn (Map.fromList . zip [(0::Int)..]) choices
  evs :: Dynamic t (Map.Map Int (Event t ())) <- listWithKey asMap (\_ s -> dynButton s)
  dynEv :: Dynamic t (Event t Int) <- mapDyn (leftmost . Map.elems . Map.mapWithKey (\k e -> fmap (const k) e)) evs
  return $ switch (current dynEv)
Run Code Online (Sandbox Code Playgroud)

这是完整文件的要点.