This is my third answer to this question. To summarize what follows: I do not longer think it is a bug. What we see is completely predictable and can be considered at most as undesirabable behaviour in a slightly pathological example.
Some terminology. I will call a displayed Dynamic
expression a dynamic object. As everything shown in a notebook, it is under control of the frontend.
Similarly, I will call a displayed DynamicModule
expression an interface. It is constructed by a kernel evaluation of DynamicModule[variables, body]
. The local variables in the DynamicModule
expression become frontend owned parameters in the interface; I will call these the states of the interface. The interface usually shows one or more controllers and one or more dynamic objects.
Every interface has a unique interface number and every dynamic object, whether displayed in an interface or not, has a unique dynamic object number.
A dynamic object updates continuously. By inspecting the LinkSnooper
output, it turns out that the updating mechanism for a dynamic object in an interface is different from the updating mechanism for a dynamic object not in an interface.
Let us first consider a dynamic object not in an interface. Such an object is the displayed result of the evaluation of Dynamic[expression]
. In a sense, this evaluation goes in two steps. In the first step, the kernel returns a DynamicBox
to the frontend with the expression still unevaluated. Then the frontend is going to display the object. It assigns a dynamic object number to it and then sends the expression to the kernel together with the object number. The kernel evaluates the expression and during that evaluation, it tags all symbols that are met with the object number. Then it returns the result to the frontend and the frontend displays that result.
When later on the kernel changes the value of a tagged variable, it informs the frontend that the corresponding dynamic object(s) should be updated and it removes the tagging. When the frontend wants to update the dynamic object (that is not always; the object could have been removed or be not visible on the screen), it again sends the expression and the object number to the kernel, the kernel evaluates and tags, returns the result to the frontend and the frontend displays the new result.
Not surprising: updating of a dynamic object not in an interface is triggered by the kernel.
Now a dynamic object in an interface. Such an interface is the displayed result of the evaluation of DynamicModule[variables, ... Dynamic[f[variables], ...]
. In the interface, the variables (states) belong to the frontend and the dynamic object has to show f[variables]
, or better f[states]
.
Two situations can occur. It may be that f[states]
can be evaluated by the frontend itself. About the simplest situation is
DynamicModule[{x=0.5}, Column[{ Slider[Dynamic[x]], Dynamic[x]}]]
Then the frontend does not need the kernel at all. When we use such an interface, LinkSnooper
does not show any traffic between the frontend and the kernel. Observe that the dynamic object displays a frontend owned value, not the value of a kernel expression.
Quite often, the frontend is unable to compute f[states]
. In that situation, LinkSnooper
shows that during the construction of the interface, for every state of the interface an auxiliary kernel variable (with name FE`state$nn
) is created and that these auxiliary kernel variables are tagged with the dynamic object number. When we change any of the states of the interface (e.g. by moving a slider), the frontend asks the kernel to assign the current states to the auxiliary variables, and to compute f[variables]
. The kernel returns this value and the frontend updates the interface.
Here is a typical example, where we want the dynamic object to display h[x]
, where h
is some or other privately defined or advanced kernel function. The frontend is not aware of this function, so it needs the kernel.
DynamicModule[{x = 0.5}, Panel[Column[{Slider[Dynamic[x]], Dynamic[h[x]]}]]]
It works fine. The state of this interface is the position of the slider. When we move the slider, the dynamic object updates and shows the value of h at the position of the slider.
The buggish example:
DynamicModule[{x=RandomReal[]}, Row[{Button["new value", x=0; Pause[1]; x=RandomReal[], Method->"Queued"], Spacer[10], Dynamic[x]}]]
When the interface is constructed, the kernel is not needed for the contents of the dynamic object. No auxiliary variables are created. When we press the button for the first time, the frontend finds that it cannot execute the button action. Only now the auxiliary variable for the state is created. This creation was triggered by the button, a button cannot be updated, so the auxiliary variable cannot be tagged. Then the button action is send to the kernel for evaluation. It assigns 0 to the auxiliary variable, pauses for a second and then assigns a random real to this variable. Finally it returns this value to the frontend and the frontend displays the result. Since the auxiliary variable is not tagged, the assignments do not result in an updating. Therefore the first number 0 does not turn up.
Now that we know why the 0 does not turn up, we also know the remedy: we have to take care that, when the interface is constructed, the frontend finds that it has to use the kernel for the computation of the dynamic object. Then automatically an auxiliary variable for the state is created and tagged with the dynamic object number.
DynamicModule[{x=RandomReal[]}, Row[{Button["new value", x=0; Pause[1]; x=RandomReal[], Method->"Queued"], Spacer[10], Dynamic[$[]; x]}]]
The contents of Dynamic
is $[];x
, which evaluates to x
. The frontend cannot handle this $[];x
(I will discuss some simpler arguments soon), so an auxiliary variable is created and tagged with the dynamic object number. When the button action is sent to the kernel, the kernel assigns 0 to this variable, which forces an updating of the dynamic object (not of the interface!), pauses a second and then assigns a random real, which also gives an updating of the dynamic object. Then the result is sent back to the frontend and the frontend processes the new state of the interface, but that does not change the display any more.
It is clear that we could have used any expression that evaluates to x
as argument of Dynamic
, as long as the frontend cannot evaluate that expression with x
replaced with the state. Before version 12, we could use expressions such as x+0
or 1*x
, because of the frontend could not do arithmetic. But in version 12 the frontend can evaluate these expressions. (It indeed looks like the frontend has its own small kernel, and that this frontend kernel is enlarged in version 12.)
When we use the option TrackedSymbols->Full
(or Automatic
or :>{x}
), we also force the immediate creation of an auxiliary variable:
DynamicModule[{x=RandomReal[]},Row[{Button["new value",x=0; Pause[1]; x=RandomReal[],Method->"Queued"],Spacer[10],Dynamic[x, TrackedSymbols:>Automatic]}]]
However, that this works has as such nothing to do with tagged variables. As with the other solutions, it lets the frontend create a tagged auxiliary variable. Only the dynamic object is updated. In my concluding example the slider does not move to 0 when we press the button.
DynamicModule[{x=RandomReal[]},Column[{Button["new value",x=0; Pause[1]; x=RandomReal[],Method->"Queued"], Slider[Dynamic[x]],Dynamic[x, TrackedSymbols:>Automatic]}]]
DynamicModule[{x, z = {0}}, Panel[Column[{Dynamic[x], Dynamic[Identity[x]], Dynamic[Sequence[x]], Dynamic[# &@x], Dynamic[1*x], Dynamic[x^1], Dynamic[{x}], Dynamic[Sequence @@ {x}], Dynamic[z], Button["press", x = 0; z = {0}; Pause[2]; x = RandomInteger[{1, 9}]; z = {RandomInteger[{1, 9}]}, Method -> "Queued"]}]]]
$\endgroup$TrackedSymbols -> All
and it's missing the update (no idea why). Any other reasonable setting gets it rightDynamicModule[{x, z}, {Dynamic[x, TrackedSymbols -> Full], Dynamic[List@x]}, Initialization :> (x = 1; SetOptions[EvaluationNotebook[], CellEventActions :> {"UpArrowKeyDown" -> (x++;)}])]
$\endgroup$DynamicModule[{x = 1, z}, {Dynamic[x], Dynamic[List@x]}, Initialization :> (SetOptions[EvaluationCell[], CellEventActions :> {"UpArrowKeyDown" -> (x++;)}])]
(which doesn't work), withDynamicModule[{x = 1, z}, EventHandler[{Dynamic[x], Dynamic[List@x]}, {"UpArrowKeyDown" :> (x++;)}]]
(which works) Perhaps it can't be explained by the behavior ofDynamic[ ]
alone, but it's its coupling with other constructs. $\endgroup$