reflex icon indicating copy to clipboard operation
reflex copied to clipboard

Inconsistent Dynamic values

Open JBetz opened this issue 6 years ago • 4 comments

I'm trying to create a Dynamic list that only updates when the length of the list changes. The length is then used to generate a list of indices that is used to create a Dynamic for each list item.

The issue is that when an item is removed from the list, the Dynamic that holds the length of this somehow doesn't change before the elements are retrieved, so it throws an out of bounds error. Furthermore, if I use traceDyn and modify the index function to ignore invalid indices, I noticed that it iterates over the list twice: once with the old length (which errors) and again for the new length (which doesn't error). So it looks like the Dynamic is somehow getting updated twice, but I have no idea why.

So, my questions are:

  1. why is the index method being called before the list length dynamic has been updated?
  2. why does pressing the remove button cause two update events?
testW :: forall t m. MonadWidget t m => m ()
testW = mdo
  listD <- foldDyn (\x xs -> if x then 1 : xs else tail xs) [] (leftmost [True <$ addE, False <$ removeE])
  addE <- button "add"
  removeE <- button "remove"
  listLengthD <- holdUniqDyn $ length <$> listD
  dyn_ $ ffor listLengthD $ \listLength ->
    el "ul" $ sequence_ $ (\i ->
      el "li" $ do
        listItemD <- holdUniqDyn $ (!! i) <$> listD
        dyn_ $ ffor listItemD $ \_ -> text "X"
    ) <$> [0 .. listLength - 1]

Alternatively, a better approach to this problem would be appreciated. This is a fairly general problem and I'm sure there are better patterns.

UPDATE: I was referred to these solutions, which I'm going to use instead. Regardless, the behavior here seems wrong, or at least not what I expected, so in any case it would be useful to understand it.

JBetz avatar Sep 16 '18 20:09 JBetz

  1. The problem is that dyn_ defers it's rendering, so when you remove an item - the inner listItemD will update before the outer dyn_ replaces everything with the shorter list.

  2. listD causes listLengthD and each listItemD to be updated

A better way to handle this is with one of the container functions. For example simpleList :: MonadWidget t m => Dynamic t [v] -> (Dynamic t v -> m a) -> m (Dynamic t [a])

On Mon, Sep 17, 2018 at 8:06 AM Joseph Betz [email protected] wrote:

I'm trying to create a Dynamic list that only updates when the length of the list changes. The length is then used to generate a list of indices that is used to create a Dynamic for each list item.

The issue is that when an item is removed from the list, the Dynamic that holds the length of this somehow doesn't change before the elements are retrieved, so it throws an out of bounds error. Furthermore, if I use traceDyn and modify the index function to ignore invalid indices, I noticed that it iterates over the list twice: once with the old length (which errors) and again for the new length (which doesn't error). So it looks like the Dynamic is somehow getting updated twice, but I have no idea why.

So, my questions are:

  1. why the index method is being called before the list length dynamic has been updated?
  2. why does pressing the remove button cause two update events?

testW :: forall t m. MonadWidget t m => m () testW = mdo listD <- foldDyn (\x xs -> if x then 1 : xs else tail xs) [] (leftmost [True <$ addE, False <$ removeE]) addE <- button "add" removeE <- button "remove" listLengthD <- holdUniqDyn $ length <$> listD dyn_ $ ffor listLengthD $ \listLength -> el "ul" $ sequence_ $ (\i -> el "li" $ do listItemD <- holdUniqDyn $ (!! i) <$> listD dyn_ $ ffor listItemD $ _ -> text "X" ) <$> [0 .. listLength - 1]

Alternatively, a better approach to this problem would be appreciated.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/reflex-frp/reflex/issues/231, or mute the thread https://github.com/notifications/unsubscribe-auth/ABE45erNugDs5onvCj2HJ0HId0hX4Slaks5ubq9fgaJpZM4WrBJ7 .

oliver-batchelor avatar Sep 16 '18 20:09 oliver-batchelor

Okay, that's starting to clear things up for me. Still have a couple questions though.

  1. listD causes listLengthD and each listItemD to be updated

Do the two events happen in the same frame?

The problem is that dyn_ defers it's rendering, so when you remove an item - the inner listItemD will update before the outer dyn_ replaces everything with the shorter list.

Do dynamic widgets always update this way? I.e., from inside out.

A better way to handle this is with one of the container functions. For example simpleList :: MonadWidget t m => Dynamic t [v] -> (Dynamic t v -> m a) -> m (Dynamic t [a])

Thanks. I just refactored to use this and it's much cleaner. :)

JBetz avatar Sep 17 '18 09:09 JBetz

I've thought about this a little, and perhaps I was wrong about some of this. I believe I was wrong, dyn_ is deferred but only for it's initialisation so this isn't the problem here.

Every time you update listD you're creating a whole new set of listItemD's. The way dyn_ works is to replace the contents of the inner widget completely every time the event fires.

So if you click add four times you're calling this code 10 separate times. The first time creates 1 listItemD, second time 2 listItemD's, third time 3 listItemD's etc. listItemD <- holdUniqDyn $ (!! i) <$> listD

When you click remove it does update immediately, however the last set of listItemD's still exists at that point. It's only gone on the next frame. When you click remove you're deleting an element off the list immediately but the listItemD corresponding to the last index is also still exists and causes the indexing error.

Hopefully that is more clear.

oliver-batchelor avatar Sep 17 '18 20:09 oliver-batchelor

When you click remove you're deleting an element off the list immediately but the listItemD corresponding to the last index is also still exists and causes the indexing error.

Okay, so I can't use a Dynamic a where computing a might fail, because reflex will try to compute it even in situations where any dynamics that it has been derived from no longer exist. Is that correct?

JBetz avatar Sep 28 '18 09:09 JBetz