19
$\begingroup$

I have two dynamic objects, one nested in the other. Both have their own functions defined, as follows. I have simplified them to the point where they don't make much sense, but can still illustrate my problem. When evaluated like this, it works as expected:

ClearAll[inner, outer];
inner[Dynamic[x_, args___]] := DynamicModule[{updateInner},
   updateInner[] := (Print@"inner"; args@1);
   Slider[Dynamic[x, (updateInner[]; x = #) &]]
   ];
outer[Dynamic[x_, args___]] := DynamicModule[{updateOuter},
   updateOuter[] := (Print@"outer"; args@1);
   {inner[Dynamic[x, (updateOuter[]; x = #) &]], Dynamic@x}
   ];

x = 1;
outer[Dynamic@x]

Any movement of the slider produces an "inner" - "outer" pair.

However, when I rename both update functions to the same update, outer will use the inner update instead of its own.

ClearAll[inner, outer];
inner[Dynamic[x_, args___]] := DynamicModule[{update},
   Print@{"inner:", SymbolName@update};
   update[] := (Print@"inner"; args@1);
   Slider[Dynamic[x, (update[]; x = #) &]]
   ];
outer[Dynamic[x_, args___]] := DynamicModule[{update},
   Print@{"outer:", SymbolName@update};
   update[] := (Print@"outer"; args@1);
   {inner[Dynamic[x, (update[]; x = #) &]], Dynamic@x}
   ];

x = 1;
outer[Dynamic@x]
 {outer:, update$337}
 {inner:, update$338}

This issues $RecursionLimit::reclim2: Recursion depth of 1024 exceeded during evaluation of x=1. >> and only "inner" is printed, never "outer". Note, that the two symbol names are (inner and outer update) not identical: update$337 and update$338.

Of course I can always rename one update function to something else, but relying on renaming when seeking robustness is never ok. How to design GUI objects that have update functions and can be safely embedded in outer GUI objects, with function-forwarding using the second argument of Dynamic?

Localizing update functions with a Module is not a good idea as the update function might rely on dynamic variables (on my real case it does).

$\endgroup$
7
  • 4
    $\begingroup$ The problem is that DynamicModule simply adds two $ signs after the local variables. So, both inner and outer uses update$$ for the function name. Since (x = #; update[]) & is a pure function and is unevaluated, it simply becomes (x = #; update$$[]) &. inner defines update$$ after outer, so the definition set by outer is not used. Try: Module[{x}, {x, Module[{x}, x]}] and DynamicModule[{x}, {x, DynamicModule[{x}, x]}]. $\endgroup$ Commented May 28, 2016 at 18:18
  • $\begingroup$ @JHM your comment is quite insightful. Would you consider turning it into an answer? $\endgroup$
    – MarcoB
    Commented May 28, 2016 at 18:34
  • $\begingroup$ @JHM Please see my edit. $\endgroup$ Commented May 28, 2016 at 18:55
  • $\begingroup$ @Kuba I hope my answer clears things up. $\endgroup$ Commented May 28, 2016 at 23:21
  • $\begingroup$ Wrap inner[] with Dynamic@inner[]? $\endgroup$
    – Michael E2
    Commented May 29, 2016 at 5:43

4 Answers 4

15
$\begingroup$

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 DynamicModules. 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.

$\endgroup$
10
  • $\begingroup$ Thanks a lot @John, especially for making it authoritative so that I know which path I should take. This will take some time to process, but I have two Q's in advance: 1) If I put all updating into kernel functions then I have to pass each dynamic variable during interactive updating to the kernel, possibly involving huge objects. Isn't that something to be avoided? 2) If I use your inner definition (with centext-localized innerDisplay), and adapt my outer to be similar (with an outer'Private context, calling inner inside outer'outerDisplay), the resulting slider does not move. $\endgroup$ Commented May 30, 2016 at 14:46
  • 1
    $\begingroup$ @IstvánZachar, the slider didn't move in your original code, either! The definitions of your updating functions in your examples, which I copied, do args@1. When you pass the outer update into the inner update, this has the effect of resetting x to 1 after you set it to your original value. I.e., it's effectively doing x=#;(*some code*);x=1;(*some more code*). Since you're now suggesting this isn't your intent, then I'm now thinking that, at least in this distilled example, your 2nd argument of Dynamic is fairly confused and merits some more thought on your part. $\endgroup$
    – John Fultz
    Commented May 31, 2016 at 14:08
  • $\begingroup$ @IstvánZachar, having the implementation live entirely in the FE is efficient. But efficiency isn't always the most important consideration. Lately, I've been seeing too many people create interfaces which effectively hardcode a specific control implementation right into the notebook. This is very unfortunate...it means that those notebooks cannot benefit from bug fixes, visual refreshes, or feature additions without being re-created from kernel code. In the future, we'll support more compartmentalization of control-like things in TemplateBox, but that's not yet a serious option. $\endgroup$
    – John Fultz
    Commented May 31, 2016 at 14:14
  • $\begingroup$ Pardon me, you're right, it did not move in my post, because in the process of posting I mixed up the order. It is meant to be: Slider[Dynamic[x, (update[]; x = #) &]]. So yes, your method works as expected, thanks again! $\endgroup$ Commented May 31, 2016 at 16:08
  • 1
    $\begingroup$ @IstvánZachar, the typo in your code, which I preserved in my code, is prone to cause confusion to future folks who look at this. I'm not entirely certain what you intended the rhs of update[args___] to be (just the Print statement?), but it'd be nice to make it something more sensible in both your question and my response. $\endgroup$
    – John Fultz
    Commented Jun 6, 2016 at 17:54
10
$\begingroup$

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 3177s 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.

$\endgroup$
1
  • 1
    $\begingroup$ That is indeed the culprit, thanks for the analysis. But then how one supposed to update dynamic variables with reusable functions in a safe way? How does WRI do it? They do not use dedicated functions like update in controls? $\endgroup$ Commented May 29, 2016 at 0:02
10
$\begingroup$

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...

$\endgroup$
9
  • $\begingroup$ Thanks Albert, will consider these, but needless to say, I'm not a big fan of unnecessarily assigning a new namespace to a new function. What I would really want to know is how WRI solves this problem. There are many such subtle issues with dynamic controls but there is no comprehensive documentation on how to design robust controls - though they obviously achieve it somehow. $\endgroup$ Commented May 29, 2016 at 13:20
  • $\begingroup$ I understand that you would like a better solution -- but that can only be provided by WRI. I actually think that this might be part of the problem: when they have such problems, they have of course all the possibilities to solve them. Well, maybe someone will see this and give better explanations and workarounds/solutions... $\endgroup$ Commented May 29, 2016 at 15:18
  • $\begingroup$ AlbertRetey and @IstvánZachar while I find J.Fultz's points, about keeping procedures in Kernel, valid I don't quite think it solves the scoping problem. Here's more localized example: f[x_] := Framed[x, Background -> LightBlue]; element[Dynamic[y_]] := DynamicModule[{x = 0}, {Slider[Dynamic@x], f@Dynamic[y]} ]; main[] := DynamicModule[{x}, Column[{ element[Dynamic[x]], f /@ {Slider[Dynamic[x]], Dynamic[x]} }] ]; - I have put a blue background around items that are expected to share value.... $\endgroup$
    – Kuba
    Commented Jun 27, 2016 at 17:32
  • 1
    $\begingroup$ @Kuba, this would require a concept of $ModuleNumber to be handled natively by the FE in the typesetting. Which could not be relied upon in the global namespace, so we'd have to be constantly rewriting the typesetting based upon the global state of the FE. Right now, the only concept of $ModuleNumber is one attached to the kernel instantiation, which does not require typesetting and can be different in different sessions. I'm not saying that would have been impossible to do this, but implementation would have been quite tricksy. $\endgroup$
    – John Fultz
    Commented Jun 27, 2016 at 19:03
  • 2
    $\begingroup$ @Kuba, the case outlined here is equivalent to something like Module[{x}, Function[{y}, Module[{x}, {x, y}]][x]]. Except in typesetting. Module evaluates away on us after doing its replacement, but DynamicModuleBox is a persistent creature, and we must always be able to determine which DynamicModuleBox we want each variable to target in order to solve the problem. You have to do that by renaming the variables in the typesetting. If you're not thinking of this as a typesetting problem, you're not properly understanding the problem. $\endgroup$
    – John Fultz
    Commented Jun 27, 2016 at 19:27
3
$\begingroup$

JHM is right about the reason. John Fultz shows nice versioning and coding style I'm encouraging to use. Yet there is no answer to how to avoid this problem in general and flexible way.

Here is my attempt, I will skip JF enhancement to not complicate things but it can be used with this method too.

The problem is that whatever symbol name we use it may happen that the collision will occur as soon as our name will already be somewhere on $ContexPath (at a time of parsing those two controllers). It would be nice to don't have to come up with unique symbols which are not readable and handy, for every piece of code we write.

The solution is to ensure that the context in which update is parsed will be unique.

We can achieve this sticking to two rules:

  • each "modular" gui element should be defined in own "subcontext"

    So e.g. between Begin["`inner`"]/End[]

  • DynamicModule variables should be typed with a backtick `update so that we are sure that even when update is somewhere in $ContexPath "our" update will be `inner`update.

Here is a minimal example of such package:

ClearAll["GUI`*"];
BeginPackage["GUI`"];

  inner; outer;
  update (*it isn't part of the solution, it is here to simulate \
           "update" being on context path*)

Begin["`Private`"];

Begin["`inner`"];

  inner[Dynamic[x_, args___]] := 
    DynamicModule[{`update}, Print@{"inner:", SymbolName@`update};
      `update[] := (Print@"inner"; args@1);
      Slider[Dynamic[x, (`update[]; x = #) &]]];

End[];

Begin["`outer`"];

  outer[Dynamic[x_, args___]] := 
    DynamicModule[{`update}, Print@{"outer:", SymbolName@`update};
     `update[] := (Print@"outer"; args@1);
     {inner[Dynamic[x, (`update[]; x = #) &]], Dynamic@x}];

End[];

End[];

EndPackage[];

Now, since we have GUI`Private`inner`update and GUI`Private`outer`update there is no collision. Which was achieved with minimal effort and works even when GUI`update is on $Path during parsing.

x = 1;
outer[Dynamic@x]

Prints inner/outer

$\endgroup$
3
  • $\begingroup$ This is interesting, I didn't know that you can specify variables starting with a backtick. What is its exact effect? It forcefully calls the variable in the given local context? In this way one can safely differentiate a local and a global variable of the same apparet name, without actually writing out the full context name? Is this feature documented? $\endgroup$ Commented Nov 28, 2016 at 17:08
  • 1
    $\begingroup$ @IstvánZachar *`update forces to use update from $Context even if there is another update found on $ContextPath. It is documented in Contexts. So in this case it will be GUI`Private`inner`update even though there is already GUI`update on $ContextPath. $\endgroup$
    – Kuba
    Commented Nov 28, 2016 at 17:45
  • $\begingroup$ Thanks Kuba for the information, here goes the +1! $\endgroup$ Commented Nov 28, 2016 at 20:58

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.