Почему этот код Reflex приводит к тому, что Dynamics запускается бесконечно с одним и тем же значением?

Цель этой маленькой программы - показать три кнопки, причем метка третьей кнопки изначально была "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 ответ

Решение

Похоже, ваш код для 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 0.

Причина, по которой вы видите цикл, заключается в следующем:

  1. main корма foo с его начальным значением 0 в options
  2. новые варианты построены
  3. listChoiceWidget получает новые опции и список обновляется
  4. первый ключ результирующей карты Ints to Events обновлен
  5. foo получает ключ обновленного события от listChoiceWidget
  6. вернуться к шагу 2 до бесконечности

Вместо того, чтобы извлекать первый ключ из карты, вам нужен какой-то способ определения последнего нажатия кнопки. Ваша карта уже содержит события нажатия для каждой отображаемой кнопки. Прямо сейчас эти события имеют тип 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: "Создать новое событие, которое происходит, если происходит хотя бы одно из событий в списке. Если несколько событий происходит одновременно, они складываются слева с помощью данной функции".

Наконец, нам нужно конвертировать ваши Dynamic t (Event t Int) в Event t Int, Мы собираемся использовать switch, который занимает Behavior t (Event t a) и возвращает Event t a, Итак, следующая строка приведет к Event t Int,

switch (current dynEv)

current извлекает Behavior из 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)

Вот суть полного файла.

Другие вопросы по тегам