0
$\begingroup$

Inside a Block, I have (among other things) a string output that is updated during an internal for loop in the following (schematic) way:

dynamicTest[] := 
Block[{dynamicstr, i},
 Print["current value: ", Dynamic[dynamicstr]];
  For[i = 1, i <= 10, i++,
     dynamicstr = ToString[i]; Pause[1];
  ];
 dynamicstr = "Done";
];

This function is now called multiple times in another place which results in the following behavior that I cannot explain: When I call the Block the second time, the output cell from the first call is also updated again, i.e. both outputs start running from 1 to 10 instead of the first staying at the final value 'Done' while the second runs from 1 to 10.

I realized that this does not happen when I use Module instead, but I do not understand exactly why. I would appreciate if someone could explain this to me or point out where it says why that is the case (in case I have missed something in the documentation).

Also, is it possible to use a Block and still reach the desired behavior?

$\endgroup$
2
  • $\begingroup$ Module does assigns new names for the local variable. Instead Block only clears the content of the local variables and restores it at the end. However, it uses the same variables. $\endgroup$ Commented Aug 11, 2023 at 10:01
  • $\begingroup$ I can highly recommend reading this tutorial $\endgroup$ Commented Aug 11, 2023 at 10:02

2 Answers 2

1
$\begingroup$

Intro

I'll try to give some quick principals to guide one's thinking about scoping and Dynamic[]. Dynamic[] is complicated, and this answer will not explain everything. The Quiz at the end probably reveals this. Perhaps the OP doesn't even need Dynamic[], as has been suggested. Perhaps one's actual use-case does need Dynamic[]. In any case, here is the basic beginning advice: The variables inside Dynamic[] should be global on purpose or inside a DynamicModule[] containing the Dynamic[]. For instance, I use Dynamic[] on purpose to monitor the progress of global variables in a time-consuming computation.

Some principals of scoping

Dynamic[code] lives "forever," until the output is deleted from the notebook. It outlives kernel sessions. It outlives front end sessions. It is reevaluated when notebooks are reopened. If it is to work "forever," then the dependencies of code have to live "forever." DynamicModule[] is typically the way one makes this happen (see further down).

Block[{vars}, code] hijacks the global variables in vars, saves their values, clears (and optionally initializes) them for the execution of code. This lasts until Block[] has finished evaluating. Any changes to the values of vars happen to the global variables. (This is useful for temporarily resetting system parameters like $RecursionLimit or $MinPrecision etc.) Any external Dynamic[] that depends on vars will be updated with the new values. When Block[] is finished and the old values are restored, Dynamic[] will again reevaluate its code.

Module[{vars}, code] creates unique global symbols based on the symbol names of vars. They have the attribute Temporary, which means that if the symbols do not leak out of the module, they will be removed after Module[] is done. Module[] variables could leak out like this: Module[{x = 1}, Dynamic[x]]. When the kernel session is over, the module variable x will be removed. and Dynamic[x] will display the unique symbol name for x that the Module[] created. The name will vary by session; see $ModuleNumber. Module[] is somewhat similar to Block[], but its creating new symbols for the vars is important. It explains why the OP sees different behavior for Module[]. It means that every call to Module[] creates a new and distinct symbol for x; whereas Block[] reuses the same global x each time, and other any other process may use the same x, too.

DynamicModule[{vars}, code] lives "forever," until the output is deleted from the notebook. It stores values for vars from session to session (as text in the notebook). The values will be in the notebook for Dynamic[] to use when the notebook is reopened (provided it is inside a DynamicModule[] and displayed in the output cell). These variables are "localized" each time the dynamic output is instantiated. Instantiation includes when you copy a DynamicModule[] output and paste in a notebook (same or different one, it does not matter). The vars are localized in a similar way to Module[]: Unique symbols names are created for each instance. They are recreated, possibly with different names, when the notebook is reopened. The new names are substituted for the vars in the instantiation process.

[*Re "global": Arguably every symbol in Mathematica is global. There is the "Global`" context, which is for the user-global symbols. But all symbols are accessible, and they are writable if not Protected or Locked. By "global" above, I mean principally the symbols in the "Global`" and "System`" contexts.]

Block[{x = 1}, code]

Block[{x = 1}, code] expresses an intention of temporarily resetting the global value of x while code is executing. The temporary aspect suggests that Block[{x = 1}, Dynamic[x]] is a mistake, as it expresses simultaneously that x should temporarily be changed as well as permanently be part of the evaluation notebook. One might imagine reasons for doing this on purpose, and therefore, it is not treated as an error. Mathematica will run the code. You just get red highlighting, which you could change in Preferences, if it annoys you.

Output from DynamicModule[{x = 1}, code]

DynamicModule[{x = 1},
 Print@x;
 Print@Dynamic@x;
 Dynamic@x]
(*  1  *) 

(*  x$20686492  $*)

(*  1  *)

The Print[e] cells contain their output outside any DynamicModule[]. In the first output line, the value of x is printed. In the second, because Dynamic[] holds x, we get a module variable with a module number tacked on (invisibly wrapped in Dynamic[]. The third output is a DynamicModule[]:

% // InputForm

(*  DynamicModule[{x = 1}, Dynamic[x], DynamicModuleValues :> {}]  *) 

Note that it is note the same module. The body code, a CompoundExpression[], has been evaluated in a module setting, and what we get is the DynamicModule[] wrapped around the value of the body.

Quiz

What variable does the Slider[] control?:

A.

 DynamicModule[{x = 1},
  Print@x;
  Print@Dynamic@x;
  Print@Slider@Dynamic@x;
  {Dynamic@x, Dynamic@Hold@x}]

B.

 DynamicModule[{x = 1},
  Print@x;
  Print@Dynamic@x;
  {Dynamic@x, Dynamic@Hold@x, Slider[Dynamic@x]}]

Bonus: Why is the x different in Print@Dynamic@x and Dynamic@Hold@x?

$\endgroup$
0
$\begingroup$

Your problem doesn't require the use of Dynamic. In fact, you may have noticed that dynamicstr is highlighted in red inside Dynamic[dynamicstr] because it's not quite correct to use Block or Module with Dynamic; DynamicModule is recommended instead.

If I remove the Dynamic from your code and add some extra Print statements, I get the following:

dynamicTest[] := 
Block[{dynamicstr, i},
 Print["current value: ", dynamicstr];
  For[i = 1, i <= 10, i++,
     dynamicstr = ToString[i];Print[dynamicstr];
  ];
 dynamicstr = "Done";Print[dynamicstr];
];


dynamicTest[]

current value: dynamicstr

1

2

3

4

5

6

7

8

9

10

Done

(I also removed the Pause.)

Running it a second time:

dynamicTest[]

current value: dynamicstr

1

2

3

4

5

6

7

8

9

10

Done

Using Module instead of Block makes no difference.

dynamicTest2[] := 
Module[{dynamicstr, i},
 Print["current value: ", dynamicstr];
  For[i = 1, i <= 10, i++,
     dynamicstr = ToString[i];Print[dynamicstr];
  ];
 dynamicstr = "Done";Print[dynamicstr];
];


dynamicTest2[]

current value: dynamicstr$29032

1

2

3

4

5

6

7

8

9

10

Done

dynamicTest2[]

current value: dynamicstr$29035

1

2

3

4

5

6

7

8

9

10

Done

To get the behavior you want, you'll need to move your variable outside of any scoping construct and make it global. Scoping constructs localize their variables lexically or dynamically, to prevent retaining values set by code inside across calls.

dynamicstr;
dynamicTest3[] := 
Block[{i},
 Print["current value: ", dynamicstr];
  For[i = 1, i <= 10, i++,
     dynamicstr = ToString[i];Print[dynamicstr];
  ];
 dynamicstr = "Done";Print[dynamicstr];
];


dynamicTest3[]

current value: dynamicstr

1

2

3

4

5

6

7

8

9

10

Done

dynamicTest3[]

current value: Done

1

2

3

4

5

6

7

8

9

10

Done

Notice that for the second run, the variable is still set to "Done" from the first pass.

$\endgroup$

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.