简体   繁体   English

为什么此Reflex代码会导致Dynamics以相同的值无限期触发?

[英]Why does this Reflex code result in Dynamics firing indefinitely with the same value?

The intent of this small program is to show three buttons, with the third button's label initially being "0" and afterwards being the index of the last-clicked button. 这个小程序的目的是显示三个按钮,第三个按钮的标签最初为“ 0”,之后为最后单击的按钮的索引。 For now the number of buttons and the labels of the other buttons are constant. 目前,按钮的数量和其他按钮的标签是恒定的。

When I compile this self-contained file with ghcjs and load Main.jsexe/index.html in the browser, I can see the two traceDyns firing in a loop, both always having the value 0. As far as I understand, nothing should happen until a button is clicked, because the _el_clicked feeds the rest of the system. 当我用ghcjs编译此自包含文件并在浏览器中加载Main.jsexe / index.html时,我可以看到两个traceDyns循环触发,它们的值始终为0。据我了解,什么也不会发生直到单击一个按钮为止,因为_el_clicked可以馈送系统的其余部分。

Also, note that I'm using mapDyn (fst . head . Map.toList) in order to extract the index of the selected button - I'm not sure this is correct, but either way I don't know what causes the infinite looping. 另外,请注意,我正在使用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)

It looks like your code for listChoiceWidget is throwing away the click events constructed by dynButton. 看来您的listChoiceWidget代码正在丢弃dynButton构造的click事件。

listWithKey returns m (Dynamic t (Map ka)) . listWithKey返回m (Dynamic t (Map ka)) In your case, the keys are of type Int and the values are Event t () (produced by dynButton). 在您的情况下,键的类型为Int ,其值为Event t () (由dynButton生成)。

On this line: 在这行上:

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

You are turning the Dynamic t (Map Int (Event t ())) into a Dynamic t Int but, crucially, you're not doing so when a click event fires. 您正在将Dynamic t (Map Int (Event t ()))转换为Dynamic t Int但至关重要的是,单击事件触发时,您并没有这样做。 This line maps over evs and produces a Dynamic that will always contain the first key in the Map of Ints to Events, regardless of whether an event has fired or not. 该行在evs上进行映射,并生成一个Dynamic,它将始终在Int to Events映射中包含第一个键,而不管是否触发了事件。 It will always be a Dynamic containing the Int 0. 它将始终是包含Int 0的Dynamic。

The reason you're seeing a loop is because: 您看到循环的原因是:

  1. main feeds foo with its initial value of 0 into options mainfoo的初始值为0馈入options
  2. new options are constructed 构建新的选项
  3. listChoiceWidget receives the new options and the list is updated listChoiceWidget接收新选项,并且列表已更新
  4. the first key of the resultant Map of Ints to Events is updated 生成的“事件到事件”映射的第一个键已更新
  5. foo receives the key updated event from listChoiceWidget foolistChoiceWidget接收键更新事件
  6. back to step 2 ad infinitum 返回第2步,无限

Instead of retrieving the first key out of the Map, you need some way of determining the last button click event. 除了从地图中检索第一个键外,您还需要一些确定最后一个按钮单击事件的方法。 Your Map already contains click events for each button displayed. 您的地图已经包含每个显示按钮的点击事件。 Right now those events have the type Event t () , but what you really need is Event t Int , so that when an event fires you can tell which button it came from. 现在,这些事件的类型为Event t () ,但您真正需要的是Event t Int ,这样,当事件触发时,您可以知道它来自哪个按钮。

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

evs' has the type Dynamic t (Map Int (Event t Int)) . evs'的类型为Dynamic t (Map Int (Event t Int)) Next we need some way of combining our events so that we have one event that fires with the most recently clicked button's key. 接下来,我们需要一种组合事件的方式,以便我们使用最近单击的按钮的键触发一个事件。

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

dynEv now has the type Dynamic t (Event t Int) . dynEv现在的类型为Dynamic t (Event t Int) The keys of the Map have already been baked into the events, so we don't need them anymore. Map的键已经包含在事件中,因此我们不再需要它们。 Map.elems turns our Map of events into a list of events, and leftmost allows you to combine a list of events into one event. Map.elems将我们的事件地图转换为事件列表, leftmost允许您将事件列表合并为一个事件。

From the docs for leftmost : "Create a new Event that occurs if at least one of the Events in the list occurs. If multiple occur at the same time they are folded from the left with the given function." leftmost文档开始 :“创建一个新事件,如果出现列表中的至少一个事件,则发生该事件。如果同时发生多个事件,则使用给定的函数从左侧折叠它们。”

Finally, we need to convert your Dynamic t (Event t Int) into an Event t Int . 最后,我们需要将您的Dynamic t (Event t Int)转换为Event t Int We're going to use switch , which takes a Behavior t (Event ta) and returns an Event ta . 我们将使用switch ,它使用Behavior t (Event ta)并返回Event ta So, the following line will result in Event t Int . 因此,以下行将导致Event t Int

switch (current dynEv)

current extracts the Behavior of a Dynamic , and switch creates "an Event that will occur whenever the currently-selected input Event occurs." current提取Behavior a的Dynamic ,和switch创建“每当当前选择的输入事件发生时将发生的事件”。

Here's the revised listChoiceWidget code. 这是修改后的listChoiceWidget代码。 I've included inline type annotations, so you'll need the ScopedTypeVariables language extension enabled to compile this code (or you can remove the annotations). 我已经包含了内联类型注释,因此您需要启用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)

Here's a gist of the complete file. 这是完整文件的要点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM