Scoping Revisited
With, Module, and Block
We finally have enough knowledge to understand the differences between the scoping constructs. We’ll start by writing the same chunk of code with each:
Module[{a=35},a^2]
(*Out:*)
1225
With[{a=35},a^2]
(*Out:*)
1225
Block[{a=35},a^2]
(*Out:*)
1225
Everything is normal as of now. So let’s try introducing some held expressions:
Module[{a=35},a^2//Hold]
Hold[a$24242]
With[{a=35},a^2//Hold]
Hold[352]
Block[{a=35},a^2//Hold]
Hold[a2]
This alone should help in figuring out what is going on:
Module
creates a new symbol with$ModuleNumber
appendedWith
inserts the given values into the expression
But what is Block
doing? Nothing seems changed.
Let’s look at a different example, just considering Module
and Block
as there is little more to discuss with With
, although it should be noted that With
is the cleanest and most useful of all these constructs.
Let’s try define a global function:
scopeProbe[x_]:=x*b;
First let’s use it inside Module
Module[{b=35},scopeProbe[10]]
(*Out:*)
10 b
This behavior makes perfect sense. The b
defined in Module
is not the global b
so this is what we’d predict.
Block[{b=35},scopeProbe[10]]
(*Out:*)
350
This here is the critical difference between Module
and Block
. Block
reassigns the values of its symbols temporarily, while Module
makes new symbols with temporary values (they have been given the attribute Temporary
).
This makes Block
incredibly useful, but also potentially dangerous. Names used as variables to Block
should always be made unique enough such that it’s unlikely they’ll have been used in a function that will be called by the Block
.
An example of how we can use this is in memoization for a recursive function:
recursiveFunction[arg_]:=Block[{`recursiveFunction`memoPad=<||>},
recursiveStep[arg]
];
recursiveStep[arg_]:=
If[!KeyMemberQ[`recursiveFunction`memoPad,arg],
`recursiveFunction`memoPad[arg]=If[Length@arg==0,
Pause[.5];
Total@ToCharacterCode@ToString@arg,
recursiveStep/@(List@@arg)//Total
],
`recursiveFunction`memoPad[arg]
];
And to see that this is doing what we expect
recursiveFunction[a[a,a,a,a,a]]//AbsoluteTiming
(*Out:*)
{0.500925`,485}
While without memoization this should take about 2.5 seconds:
recursiveNoMemo[arg_]:=If[Length@arg==0,Pause[.5];Total@ToCharacterCode@ToString@arg,recursiveNoMemo/@(List@@arg)//Total]
recursiveNoMemo[a[a,a,a,a,a]]//AbsoluteTiming
(*Out:*)
{2.506773`,485}
And just to check that we the Block
wrapper does something:
recursiveStep@a[a,a,a,a,a]
(*Out:*)
If[!KeyMemberQ[Global`recursiveFunction`memoPad,a[a,a,a,a,a]],Global`recursiveFunction`memoPad[a[a,a,a,a,a]]=If[Length[a[a,a,a,a,a]]0,Pause[0.5`];Total[ToCharacterCode[ToString[a[a,a,a,a,a]]]],Total[recursiveStep/@List@@a[a,a,a,a,a]]],Global`recursiveFunction`memoPad[a[a,a,a,a,a]]]