Internal DynamicModule steals scope of external DynamicModule
This answer builds on @AlbertRetey's answer where, in the comments, you wanted some guidance as to how Wolfram creates robust controls.
Think very carefully about the division of labor between the FE and the kernel. You've effectively embedded the entire implementation inside the FE by putting the update
functions in DynamicModule
s. Generally, this is a mistake for anything you wish to be general and nontrivial. It's better to have the implementation be in a single, versioned, interface function which exists in kernel code that can be referenced by the control.
IntervalSlider
provides a good example. IntervalSlider
typesets into something which is basically a reference to the function NotebookTools`CustomSliderDisplay
. This code is still going to generate its own DynamicModule
which, in principle, could have the same sort of naming conflicts you're pointing out. But the code has been defined now in a private context, which means all of the DynamicModule
variable names are now in their own context, and so it would basically take user maliciousness to cause a problem. And many things may not need to be represented at all in a DynamicModule
. Your specific example falls in the latter category.
So, then, your inner
definition might look something like this:
ClearAll[inner, outer, update];
Remove[update]
inner[Dynamic[x_, args___]] :=
Interpretation[
Dynamic[inner`innerDisplay[1, Dynamic[x, args]], TrackedSymbols :> {}],
inner[Dynamic[x, args]]];
Begin["inner`Private`"];
update[args___] := (Print@"inner"; args@1);
inner`innerDisplay[1, Dynamic[x_, args___]] :=
Slider[Dynamic[x, (update[args]; x = #) &]];
End[]
Your typeset Dynamic
now has exactly one kernel dependency: innerDisplay
. As long as you make sure that this is properly loaded, which might involve setting the outermost Dynamic
to load a package in its Initialization
, then you're in good shape.
The first argument of innerDisplay
is a versioning argument. It leaves you flexibility if you want to change things in the future while supporting backward compatibility with notebooks that have older constructs in them.
The cause is the variable name confusion. It is true that each DynamicModule
creates different symbol names. However, a new symbol name is generated again in Dynamic
. For example, try running this code and slide the slider:
DynamicModule[{x = 0, f}, f[] := Print[SymbolName@f];
f[]; Slider[Dynamic[x, f[] &]]]
(* f$400707 *)
(* Slider *)
(* f$$3152 *)
f$$3152
is printed when you move the slider.
Using OP's code construction + some modifications:
ClearAll[inner, outer];
inner[Dynamic[x_, args___]] := DynamicModule[{update},
update[] := (Print@update; Print@args);
update[];
Slider[Dynamic[x, update[] &]]];
outer[Dynamic[x_, args___]] := DynamicModule[{update},
update[] := (Print@update);
update[];
{Slider[Dynamic[x, update[] &]],
inner[Dynamic[x, update[] &]]}];
x = 1;
outer[Dynamic@x]
(* update$405810 *)
(* update$405811 *)
(* update$405810[] & *)
(* 2 Sliders *)
(* FE`update$$3176 *)
(* FE`update$$3177 *)
(* FE`update$$3177[] & *)
The 3176
is printed when you move the first slider (outer
). The two 3177
s are printed when you move the second slider (inner
). The first three lines of output is the expected behavior (args
refers to update
in outer (405810)
not inner (405811)
), which is correct, but the last three lines are the actual behavior (args
refers to update
in inner (3177)
not outer (3176)
).
This creates name confusion as I stated in the first comment, causing Mathematica to use the definition of update
defined by inner
for all instances of update
.
This is probably not really an answer but definitely too long for a comment.
First thing that seems worth mentioning is that the following part of the documentation for InheritScope
can be interpreted as if the behavior you see is an intended feature:
With the setting InheritScope->False, a DynamicModule will continue to inherit variable settings from a parent instance that wraps the DynamicModule instance onscreen.
I have not found and am not aware of a possibility to switch this off (InheritScope
just controls such inheritance between DynamicModule
instances which are not nested on screen).
Another detail that becomes more clear when slightly adjusting the first example in JHMs answer is why there are two symbols generated per DynamicModule
-Variable: one is generated in the kernel and one in the frontend:
DynamicModule[{x = 0, f},
f[] := Print[Context@f -> SymbolName@f];
f[];
Slider[Dynamic[x, f[] &]]
]
That is of course not much of a surprise. More interesting is that even in the frontend two different symbols for the inner and outer update are generated, so it looks like there is even extra effort involved to achieve the documented behavior.
I see at least two workarounds to overcome that problem:
Use Contexts/Namespaces
The first is to define inner
and outer
in two different private contexts. That is something that I would recommend to do anyway if you want to achieve maximal independence of your functions. You might also see my question and answer here, where I describe a different potential problem which also can be cured or at least mitigated in most cases with that approach.
Use a kernel symbol and handle lifetime of that yourself
Another approach is to use a temporary unique symbol to define the update function and remove it yourself when not needed anymore, here is an example how that could be achieved:
With[{update = Symbol[StringReplace[CreateUUID["u$"], "-" -> ""]]},
DynamicModule[{x = 0},
Slider[Dynamic[x, update[] &]],
Initialization :> (
update[] := Print[Context@update -> SymbolName@update];
update[]
),
Deinitialization :> (
Print["cleanup: ",HoldForm[update]]; Remove[update];
)
]
]
Note that with this approach you are loosing the functionality that DynamicModule
variables will automatically be stored in the Notebook
when that is saved, so you will need extra care to initialize if you need that to work. For the kind of local function definitions you have in mind that can easily be achieved with the Initialization
option as shown above, which by the way at least for me makes the code somewhat clearer by explicitly stating what part of it actually is initialization. This also makes clear that we need to generate a symbolname which will be unique even in a new session, which can either be (statistically) ensured with the use of CreateUUID
or maybe also something like: StringJoin["u$", ToString[$SessionID], "$", ToString@$ModuleNumber]
.
Another detail that even might be an advantage is that there is now no frontend symbol for update
generated anymore, which for the intended purpose seems to not be of any relevance and only adds overhead. It is probably interesting to note that due to that fact this approach can also be used to solve the problem that largish data in DynamicModule
variables sometimes makes the gui stuff become unresponsive because the largish data is passed from frontend to kernel for every update.
Of course this approach is depending on the deinitialization to work correctly and reliably, otherwise it might become a memory leak...