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 Internal`InheritedBlock
, 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.