Disclaimer
The following is non-standard, probably contentious and possibly not apt for scenarios I haven't encountered but I have found the simplification this practice introduces so useful for building large code bases that I thought I'd share it.
I now never use With, Module and only v. occasionally use Block. It's all the way with Let.
myFunc[var1_]:=Let[
var2=var1^2,
var2
]
myFunc[2]
(* 4 *)
For mine, this is cleaner, shorter and more expressive and an idiom that seems to serve all my encapsulation needs (including with Dynamic). It also has the advantage of naturally handling the very common case of both cascading local definitions
myFuncC[var1_] := Let[
var2 = var1^2,
var3 = var2^2,
{var2, var3}
]
myFuncC[2]
(* 4, 16 *)
as well as simultaneous local definitions
myFuncS[var1_] := Let[
{varA, varB} = {var1^2, var1^3},
foo[varA, varB]]
myFuncS[2]
(* foo[4,8] *)
Well the latter two use-cases were the original motivation behind Let's definition so it is of course really With (and variants) in disguise but even this extra layer on top of With comes in handy for implementing "dynamic debugging" and in unit testing by adding hooks into Let's definition.
I've adopted Let for awhile now without any issues at all but for all responsibility and maintenance take it up with the original author - Leonid :)
Very occasionally I'll use Block when needing to block a previous definition or preserve an old data structure but I shy away from any global idiom not only because of WReach's answer in describing how old values can unexpectedly intrude but also because in basic debugging/refactoring the cognitive overhead quickly mounts by having to follow more convoluted control flows.
The one exception is in building complex dynamical interfaces where the notion of a global variable makes more sense but in this case due to Dynamic/DynamicModule's semantics, IMO, an entirely new idiom using Let is needed.
The supposed use-cases for Module (changing local variables, defining local functions) I don't find persuasive. Nested changing of local variables IMO is better handled with an explicit recursive function while I don't bother with local functions since I find it makes for convoluted, less-maintainable code (not even mentioning the identified leakage, scoping and Dynamic issues). Smaller, helper functions called in Let in my experience can be more conveniently defined outside its scope.
I find these non-local helper definitions perfectly safe in an interactive session for initial experimentation/prototyping (where function-colliding is much less likely than variable-colliding). By the time function-colliding might become an issue, the code has long migrated into a package where the helpers sit safely encapsulated and ready for "public usage" if broader use is subsequently required. RIP Module.
Aside
Well OK, there is a certain situation where I need to resurrect Module. It is when a local data structure needs to be generated as in the following pseudo(ish) code.
f[a_, b_] = someHelperFunction[a, b];
myFuncP[args___] := Module[
{lds = GenerateLocalDataStructure[args]},
Let[
a = preProcessing1@args,
b = preProcessing2@args,
c = somePosition@args,
[FilledSquare];
lds = MapAt[f[a, b], c]@lds
]]
Note this involves the only wrinkle with Let that you sometimes have to be aware of - without \[FilledSquare] Let thinks it owns lds. The ; tells Let, that its local defining is over and hence that lds actually belongs to Module. (hence \[FilledSquare] is just a dummy expression - it could be anything followed by a ; - its chosen here for code readability).
Note also, that this wrinkle doesn't arise if there was a way of (mutably) modifying a datastructure that didn't use Set - see these comments. This IMO is another argument for the practice for not overloading Set where reasonable (as suggested by Leonid in that discussion) and another use for the introduction of something like @=. With the latter defined in a .m file no \FilledSquare is needed and in my set up the above looks like:
myFuncS[args___] := Module[
{lds = GenerateLocalDataStructure[args]},
Let[
a = preProcessing1@args,
b = preProcessing1@args,
c = somePositionInlds@args,
lds @= MapAt[f[a, b], c]
]]
WL's functional nature means that local variables tend to arise arise more often that local data structures. Largish data structures typically get passed in as a (held) argument and therefore with scoping that Let can't touch. This is perhaps why, in my experience, this use-case of semi-persistent, local data structures rarely arises. RIPurgatory Module.
var2may be in danger inside your function (which may also happen ifvar2has a global value and you are not careful), but that you export this variable with its value into global namespace, after you call your function, and thereby pollute that namespace. When the code base grows larger, sooner or later this will cause trouble in some completely unrelated part. So, yes, do useModuleorWithfor cases like that - this is exactly what they are for. – Leonid Shifrin Sep 21 '16 at 20:35With. UseBlockandModuleif you know why you need it (Moduleif you need to modify a variable multiple times,Blockif you need to undefine something). – masterxilo Sep 22 '16 at 01:41