In the past year I have been passionately fighting what Simon Peyton Jones calls "the effects monster", although often it feels like I am fighting windmils instead. No useful programs can be written without effects, but effects turn bad when they are observable from within the program itself. Instead we should strive for encapsulating effects such that they become harmless first class pure values, but more on that in the future. In this first installment in a longer series on the perils of side-effects, we will look at one of the most beautiful examples of observable effects, namely closures and variable capture in imperative languages.
The semantics and differences in variable capture mechanisms for closures between various programming
languages is a fascinating topic. One of my favorite examples is
was
Ruby’s scoping mechanism for blocks. The problem is acknowledged and
eloquently explained on page 105 in the second edition of the
pickaxe book. I
am particularly amused by the proposed pragmatic
solution; keep your code small so that it is easy to spot any potential problem,
and use some form of
Hungarian notation to put local variables and block parameters in a
different lexical namespace. Fortunately, this
issue is
fundamentally fixed in
Ruby 1.9; Matz does
not have to fear my wrath anymore (thanks to
Dave Remy for bringing the
remark at around 7:54 minutes into the video to my attention, which of course
might not at all be a side-effect of my rants).
To understand the semantic subtleties of variable capture in
closures, consider a variation of the example
from section 14.5.15.3.2 of the
C# 2.0 language specification with a first loop that creates a
list of closures DelayedActions, and a second loop that executes the resulting
list of closures to accumulate the results into a single string:
var DelayedActions = new List<Func<int>>();
var s = "";
for (var i = 4; i < 7; i++)
{
DelayedActions.Add(delegate(){ return i; });
}
for (var k = 0; k < DelayedActions.Count; k++)
{
s += DelayedActions[k]();
}
Console.WriteLine(s);
According to the C# language semantics, the "outer" variable i in the closure delegate(){ return i; }
is captured as an lvalue so that its lifetime properly extends its static
scope. Each iteration of the first loop updates
the same reference to variable i that is captured in each of the closures,
hence running
this program prints 777 on the console.
If you are really geeky, as I am, besides having the C# language spec as nighttime reading, you might enjoy using .NET Reflector to figure out what code the C# compiler generates to make this work.
What you see is that
the variable i is lifted into a
display
class Scoping.Program/<>c__DisplayClass3
that holds the reference to the variable i:
[CompilerGenerated]
private sealed class <>c__DisplayClass3
{
public int i;
public int <Main>b__0()
{
return this.i;
}
}
Each use of the local variable i in the loop is indirected to the
instance CS$<>8__locals4 of this display class:
var DelayedActions = new List<Func<int>>();
string s = "";
Func<int> CS$<>9__CachedAnonymousMethodDelegate2 = null;
var CS$<>8__locals4 = new <>c__DisplayClass3();
CS$<>8__locals4.i = 4;
while (CS$<>8__locals4.i < 7)
{
if (CS$<>9__CachedAnonymousMethodDelegate2 == null)
{
CS$<>9__CachedAnonymousMethodDelegate2 = new Func (CS$<>8__locals4.b__0);
}
DelayedActions.Add(CS$<>9__CachedAnonymousMethodDelegate2);
CS$<>8__locals4.i++;
}
int k;
for (k = 0; k < DelayedActions.Count; k++)
{
s = s + DelayedActions[k]();
}
Console.WriteLine(s);
Interestingly, the DelayedAction list contains just references to
exactly one delegate, in other words, the following expression DelayedActions.All(f
=> f == DelayedActions[0]) evaluates to true. According to section
14.9.8 of the C# 2.0 language specification, this is permitted, but not
required, and indeed, when we unroll the loop into three separate statements,
three non-equal delegates are created. This is a problem since removing event
handlers in C# relies on delegate equality (using the button.Click -= handler
idiom) and as a result in many cases it
becomes impossible to remove event handlers that are created as anonymous
methods or lambda expressions.
var pitifulbutton = new Button();
pitifulbutton.Click += delegate{ Console.WriteLine("Hello World!"); };
...
pitifulbutton.Click -= delegate{ Console.WriteLine("Hello World!"); }; // does not remove handler
Now consider the slightly different program. The only difference is that inside
the first loop, the local variable i is now copied into the local variable
j that is captured as an outer variable by the closure delegate(){ return
j; }.
As the C# 2.0 specification warns, closures make the effect of instantiating
local variables observable. A new local variable j is instantiated for each
repetition of the first loop:
DelayedActions = new List<Func<int>>();
s = "";
for (var i = 4; i < 7; i++)
{
int j = i;
DelayedActions.Add(delegate(){ return j; });
}
for (var k = 0; k < DelayedActions.Count; k++)
{
s += DelayedActions[k]();
}
Console.WriteLine(s);
However, the semantic effect is much more profound that this small syntactic
change. Instead of printing 777, the program now prints 456
since the lvalues captured in each closure are not shared in this case.
Looking at the generated code, we see that the generated code is much simpler
than for the previous example. Again there
is a display class, now called <>c__DisplayClass5, that instead of
the variable i now holds the variable j:
[CompilerGenerated]
private sealed class <>c__DisplayClass5
{
public int j;
public int <Main>b__1()
{
return this.j;
}
}
Inside the loop nothing special is happening to variable i anymore,
since it is not captured by the closure, but this time all access to the variable
j is indirected to the instance <>c__DisplayClass5 CS$<>8__locals6
of that class:
DelayedActions = new List<Func<int>>();
s = "";
for (int i = 4; i < 7; i++)
{
var CS$<>8__locals6 = new <>c__DisplayClass5();
CS$<>8__locals6.j = i;
DelayedActions.Add(new Func (CS$<>8__locals6.b__1));
}
for (k = 0; k < DelayedActions.Count; k++)
{
s = s + DelayedActions[k]();
}
Console.WriteLine(s);
If we would move the declaration of j out of the loop, each closure will capture the
same lvalue to a single instantiation of j and the program would print
666 instead of 777 or 456.
Now let's transliterate this sample into JavaScript. It is astonishing how close
the two programs are after a little
monkey patching to add
the method Add to the Array prototype
and defining a static class Console. The only remaining difference is creating
a new array var DelayedActions = new Array(); in JavaScript versus
creating a new list var DelayedActions = new List<func<int>>() in C#. You
have to admit that is pretty wicked.
var Console = { WriteLine : alert };
Array.prototype.Add = function(x){ return this.push(x); };
var DelayedActions = new Array();
var s = "";
for(var i = 4; i < 7; i++)
{
DelayedActions.Add(function(){ return i; });
}
var s = "";
for (var k = 0; k < DelayedActions.length; k++)
{
s += DelayedActions[k]();
}
Console.WriteLine(s);
//////////////////////////////////////////////////////
DelayedActions = [];
s = "";
for(var i = 4; i < 7; i++)
{
var j = i
DelayedActions.Add(function(){ return j; });
}
for (var k = 0; k < DelayedActions.length; k++)
{
s += DelayedActions[k]();
}
Console.WriteLine(s);
However, despite the syntactic likeness, the semantics of the two programs are completely
different. In JavaScript, the first output is 777, but the second output
is 666. The reason is that in JavaScript, the scope of variable j
is limited to the body of the first loop, but all local variables are
instantiated at the start of a routine. Another difference between the two
program is that now DelayedActions.All(f
=> f == DelayedActions[0]) evaluates to false, where the
routine All could be monkey-patched as:
Array.prototype.All = function(p)
{
for(var i=0; i < this.length; i++)
{
if(!p(this[i])) return false;
}
return true;
};
As an aside, the syntactic likeness of C# and JavaScript makes it tempting to pursue a shallow embedding of C# into JavaScript, that is to syntactically transliterate C# programs directly into "natural" JavaScript equivalents. This is contrast to deep embedding where the C# semantics, as defined by its compilation into MSIL, is meticulously encoded in completely unreadable, but correct wrt to the C# semantics, JavaScript. Shallow embedding of one language into another semantically different language as a rule results in a Frankenlanguage that is neither adheres to the source nor target language semantics.
Variable capture and substition in pure mathematics already is a hairy topic which, according to Cardone and Hindley's History of Lambda-calculus and Combinatory Logic, can be traced back to 1889. Add mutable variables to the mix and things become even more complex. Of course the best solution is to just surrender to the mathematicians and remain pure by making the effects of instantiating and mutating imperative variables explicit using the state monad.