9
$\begingroup$

Documentation bug introduced in V10 or eralier and persisting through V11.3

It is a because the undocumented optimization method by FE makes major assumptions.

The example is not artificial as it is about basic refreshing of Dynamic object 'on demand'.


trigger = True;

panel[] := DynamicModule[{x}, Framed@Dynamic[x], 
   Initialization :> (x = RandomReal[])];

Column[{
  Button["redo", trigger = ! trigger, Method -> "Queued"],
  Dynamic@trigger,
  Dynamic[trigger; panel[], TrackedSymbols :> {trigger}],
  Dynamic[trigger; {RandomReal[], panel[]}, TrackedSymbols :> {trigger}]      
}]

enter image description here

As you can see Dynamic[trigger; panel[], TrackedSymbols :> {trigger}] is not displayed correctly after button action.

What is weirder, if you check with Echo, e.g:

Dynamic[Echo@trigger; panel[], TrackedSymbols :> {trigger}]

you will see that trigger is echoed while the display does not change.

Why does it happen? Is this a bug?

This piece of code shows a use case of updating a Dynamic on demand, what is the safe way to achieve this in general?


Similar problems with Dynamic not updating properly:

What is the difference between Dynamic[x] and Dynamic[ h[x] ] for DynamicModule variables?

Why is PaneSelector caching nested Dynamics and how to switch it off?

Dynamic not responding to Refresh TrackedSymbols


reproduced on Win10 V10.4 / V11.2

[CASE:4017880] was created

$\endgroup$
6
  • $\begingroup$ Maybe a clue, if you put Dynamic[ trigger; {RandomInteger[], panel[]}, TrackedSymbols :> {trigger} ] panel only updates when RandomInteger[] changes, I assume the same thing happens with your RandomReal[], it's just less likely that RandomReal[] isn't different every time. $\endgroup$
    – N.J.Evans
    Feb 16, 2018 at 13:05
  • $\begingroup$ @N.J.Evans The last Dynamic is only for comparison and the goal is to have the first panel displayed correctly. Obviously one does not want to put random numbers everywhere just to make stuff work :) $\endgroup$
    – Kuba
    Feb 16, 2018 at 13:08
  • $\begingroup$ I understand that, the point was just that it might be a clue as to what is going on since it seems to evaluate Random...[] then compare it to the existing value before deciding whether to update. Which isn't obvious with RandomReal[]. $\endgroup$
    – N.J.Evans
    Feb 16, 2018 at 13:12
  • 2
    $\begingroup$ @N.J.Evans ok, yes. Unless I'm mistaken I had a similar idea that if FE thinks the content is the same it will not try to rerender. Notice that panel[] evaluates always to the same thing and Initialization is run later. If you change the definition to panel[] := DynamicModule[{x = RandomReal[]},... it evaluates to something different each time and this time updating works. $\endgroup$
    – Kuba
    Feb 16, 2018 at 13:18
  • $\begingroup$ If you initialize panel differently you get the behavior you want: panel2[] :=DynamicModule[{x = RandomReal[]}, Framed@Dynamic[x]]. I know that my panel2 function does not behave the same as panel but in many cases it will. In my opinion what you are highlighting is a bug that should be reported. $\endgroup$ Feb 16, 2018 at 14:43

1 Answer 1

10
$\begingroup$

There is an optimization in the front end that's catching this case. Specifically, when the FE requests the evaluation of the Dynamic, it checks to see if the resulting expression that came back is different from the previous expression. If it's the same, then it does not destroy and recreate the boxes.

Since this is a typeset DynamicBox, it's sending a ToBoxes evaluation. The result of that evaluation is:

DynamicModuleBox[{x$$}, 
 FrameBox[DynamicBox[ToBoxes[x$$, StandardForm]], 
  StripOnInput -> False], Initialization :> (x$$ = RandomReal[]), 
 DynamicModuleValues :> {}]

The fact that trigger changed did cause it to re-evaluate, but when it evaluated, it returned exactly the same thing, so the front end optimization kicks in and it decides to do nothing. Is this correct? It's not documented, but frankly that's just because it never occurred to me that this would be an issue. If this must rise above the level of undefined behavior, then I'd be inclined to keep the current behavior and document it.

Working around this is simply a matter of ensuring that a different expression is actually returned. For example, this would do it:

trigger = True;

panel[t_] := 
  DynamicModule[{x}, Framed@Dynamic[x], 
   Initialization :> (t; x = RandomReal[])];

Column[{Button["redo", trigger = ! trigger, Method -> "Queued"], 
  Dynamic[panel[trigger], TrackedSymbols :> {trigger}]}]

Here, I've added a reference to trigger which gets passed into panel[] and embedded in the DynamicModule initialization. Then, instead of the resulting ToBoxes expression simply including Initialization :> (x$$ = RandomReal[]), it will now include Initialization :> (False; ...) or Initialization :> (True; ...), and the fact that it toggles back and forth will defeat the front end optimization.

$\endgroup$
6
  • $\begingroup$ Thanks for the confirmation. Knowing this will help to avoid this problem in future. Yet it may still be a problem if panel[] comes from a 3rd party package. What then? The way this optimization works forces certain coding guidelines. $\endgroup$
    – Kuba
    Feb 16, 2018 at 20:48
  • $\begingroup$ Overlay[{panel[], trigger}, {1}, 1], that's an option but quite silly. $\endgroup$
    – Kuba
    Feb 16, 2018 at 21:12
  • 3
    $\begingroup$ More documentation would be nice. Especially "optimizations" (which may optimize the FrontEnd, but not developer time). $\endgroup$ Feb 16, 2018 at 23:33
  • $\begingroup$ @Kuba In the absence of a true trigger mechanism in the FrontEnd Overlay is not so bad. My entry for TabView (which should get re-evaluated (with a different result)) is now just "PCA" -> Dynamic[ Overlay[{showTabPCA[], reDoTabPCA}, {1}, 1] , TrackedSymbols :> {reDoTabPCA}] and it does what I want. Not very elegant. But I don't care. $\endgroup$ Feb 16, 2018 at 23:52
  • $\begingroup$ Ha, in fact I've faced it earlier when I tried to create custom Animator Buttons to stop/run FE side iteration. I wanted to do this by showing/hiding/recreating AnimatorBoxes but I failed because recreated Animator was not recognized as a new one!~ $\endgroup$
    – Kuba
    Feb 23, 2018 at 11:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.