Computer science, programming, and whatnot.

Mathematica's Scoping is Weird

December 30, 2021 back to posts

I've been using Mathematica a little bit in the past few weeks to do some simple plotting and symbolic manipulation of equations.It's okay; I keep running into weird behavior and getting funny errors that I assume more seasoned Mathematica users would not get. Here's one of them.


Mathematica has weird scoping rules. For instance, there's a thing called With that let's you assign values to variables in some expression and then have these values be replaced in that expression. It feels similar to a regular block in C-like languages. It looks like this:

In[1]:= With[{x=1}, x+1]
Out[1]= 2

No surprise so far, since 1 + 1 == 2. However, what happens if you make a new variable in the expression in a With?

In[2]:= With[{}, inner=1]
Out[2]= 1
In[3]:= inner
Out[3]= 1

Okay, so inner has now leaked out to the global scope. Annoying, since it might be difficult to avoid having symbols leak out of your scope, but maybe it's not so bad.


Another similar form to With is Block, which is used for dynamic scoped variables. Assume we have a bunch of values that we don't want to keep passing around all functions that we use. For instance we can have a function that just adds its argument to some "global" symbol:

In[1]:= addX[a_]:=a + x

Here x is a free variable in the function addX. We can evaluate the function and assign a value to a:

In[2]:= addX[12]
Out[2]= 12+x

We can also define x to be some value.

In[3]:= x=3;
Out[4]= 15

Maybe we would like to evaluate addX but use a different temporary value for x. We can use Block for this:

In[5]:= Block[{x=10}, addX[10]]
Out[5]= 20

This does not change the value of x

In[6]:= x
Out[6]= 3

Using the other construct from the beginning, With, does not work the same way, since the addX function will already look up the global value of x when it is evaluated. In a sense, Block makes references to x give higher precedence to the Block value instead of the global value.

In[7]:= With[{x=10}, addX[10]]
Out[7]= 13

This might be surprising, but hey, different constructs for different semantics; presumably there are times when you'd want With semantics and other times when you want Block semantics.

Another problem arise when we have an old variable still in the notebook, maybe introduced from a scope you thought was local. Consider the following

In[8]:= Block[{y=10, x=y+10}, addX[10]]
Out[8]= 30

So far all is well; y is 10, x is 20, and we add 10 to x which gives us 30. What happens now if we add a global variable named y?

In[9]:= y=0;
        Block[{y=10, x=y+10}, addX[10]]
Out[10]= 20

We get a different answer! It turns out that when Block evaluates its arguments, it does so without binding the values it creates as dynamic, so the evaluation of x=y+10 does not use the newly made variable y but rather the global value 0. Unless, of course, y has no value yet, in which, I guess at a later stage, it is bound to the value introduced by Block. Makes sense? No?

Presumably this is documented somewhere in the language specification, if you just know where to look and exactly how the language works. But man, this is not intuitive.

Pointers, complaints, suggestions, and your bitcoin wallet, can be sent to my public inbox (plain text emails only).

Thanks for reading.

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License