Scoping constructs in Wolfram Language

GROUPS:
4 months ago
6 Replies
 Sander Huisman 7 Votes Thanks for sharing! very informational. I mostly use With/Module, Block very rarely. For the case of some (sub)routine, I found one generally uses Module over With because a routine generally has some temporary variable that needs to be stored/manipulated, so then one can only use Module (not With as the variables can not be manipulated).
4 months ago
 Leonid Shifrin 8 Votes Glad you found it useful. Re: Block - there are cases where it is very useful, here is one relevant M SE discussion. One difference between techniques based on Block vs those based on Module or With is that, while one can emulate Module or With reasonably well with the top-level code, one can't do the same with Block as reliably. Re: Module vs With: I prefer to use With when possible, because immutability of its variables allows to catch certain bugs, and in general it is best to avoid side effects when possible. But there is a tradeoff between being side effect - free and keeping the code readable / flexible, so I personally sometimes use Module for readability, where nested With could be used instead. An alternative would be to implement one's own scoping constructs, such as a nested With - which particular case was discussed e.g. here and then here.There is a lot more to scoping in Mathematica / WL, than is covered in my answer above. For example, there are many subtleties related to how lexical scoping is emulated with variable renaming mechanism. There have been many discussions of that, in particular I can refer to this and this, and references / links therein. Here also belongs the topic of how variable conflict resolution is performed in nested lexical scoping constructs - it has a number of subtleties (see e.g. this answer and links therein, as well as the documentation for scoping constructs).Another topic not covered above is the use of InternalInheritedBlock, which is an extremely useful scoping construct in some cases, and some other less known scoping constructs. There are also topics such as safe resource management and dynamic environments (one example can be found in this answer), which logically belong here too. Yet another topic is garbage collection, which is mostly relevant for Module (since it creates new temporary symbols). Some of the issues with this are discussed in the documentation, but there are some subtleties in how Module - generated symbols are garbage-collected, and in some cases very non-obvious memory leaks may happen. Some details on this behavior can be found in this answer, and more detailed discussion (in Russian, but one can google-translate) can be found here. The large-scale encapsulation constructs (contexts and packages) can also be viewed as scoping constructs, since they also are used to encapsulate / localize symbols and limit their visibility. Since this is a large topic in its own right, I will only mention here that there are lesser known subtleties in the interaction of those with local variables in usual scoping constructs (Module, With, Function, RuleDelayed). In this context, I would refer to this and this discussions for further details.
4 months ago
 Joe Donaldson 3 Votes For differentiating between With, Module, and Block, I find this example helpful (from the docs for With, "Examples", "Properties and Relations"): In[1]:= {Block[{x = 5}, Hold[x]], With[{x = 5}, Hold[x]], Module[{x = 5}, Hold[x]]} Out[1]= {Hold[x], Hold[5], Hold[x\$119582]} In[2]:= ReleaseHold[%] Out[2]= {x, 5, 5} 
4 months ago
 Leonid Shifrin 3 Votes It's a useful example, but to fully understand it, one needs to also understand the non-standard evaluation process and how garbage collection works, so it is by no means trivial. I would say that it illustrates some advanced uses of these constructs rather than really differentiates between them. I would rather use an example like this to illustrate a basic difference: ClearAll[i, a]; i = 1; a := i^2; Module[{i = 2}, i = 3; a] Block[{i = 2}, i = 3; a] With[{i = 2}, i = 3; a] (* 1 9 During evaluation of In[12]:= Set::setraw: Cannot assign to raw object 2. 1 *) 
4 months ago

With vs. Block example

One basic example that even new user often struggle is to insert values of parameters into an analytic solution. Let's say we have something like this

solution = DSolveValue[{y'[x] + y[x] == a Sin[x], y[0] == b}, y[x], x]

(* Out[1]= -(1/2) E^-x (-a - 2 b + a E^x Cos[x] - a E^x Sin[x]) *)


If you now want to insert values for a and b, the usual way is to use a replacement rule {a->aValue, b->bValue}. Nevertheless, users might try to insert values using

With[{a = 1, b = 1},
solution
]

(* Out[6]= -(1/2) E^-x (-a - 2 b + a E^x Cos[x] - a E^x Sin[x]) *)


which fails. As Leonid already wrote With "does all the replacements before the body evaluates" and this makes the approach fail. Module cannot be used as well, because it uses lexical scoping that introduces new variable names that only look like normal a and b. Block, however, can be used

Block[{a = 1, b = 1},
solution
]

(* Out[7]= -(1/2) E^-x (-3 + E^x Cos[x] - E^x Sin[x]) *)


Although in general, using replacement rules is the better alternative, for some cases this gives a good alternative.

 Daniel Lichtblau 3 Votes Could also do: With[{a = 1, b = 1},Evaluate@solution]. The point of Evaluate here is to get past the HoldAll attribute of With` for purposes of injecting those values.