A couple of posts on Larry O'Brien's blog piqued my interest, because they're about one of my
favourite things - anonymous delegates. Specifically:
In the first post, Larry says:
The proposal has slightly cleaner syntax than what is in C# but I believe would
reprise an issue that C# has, in that "captured outer variables" are shallow copies.
However, in the second, he contrasts the behaviour of two programs, one in C# and the other in Ruby. The
implication appears to be that Ruby treats variable capture in closures differently from C# - and that
was my first thought too. However, a brief experiment with Ruby showed that the different semantics has
nothing to do with closures at all, and in fact it's all down to C#'s for loop.
So, here's the first program, edited slightly for length (and to remove Console.ReadKey, which doesn't
play nicely with
Cygwin rxvt):
using System;
using System.Collections.Generic;
delegate void VoidDelegate();
class Program
{
public static void Main()
{
List<VoidDelegate> closures = new List<VoidDelegate>();
//Create a bunch of closures
for (int i = 0; i < 10; i++)
{
VoidDelegate myClosure = delegate
{
//Capture outer variable
Console.WriteLine(i);
};
closures.Add(myClosure);
}
foreach (VoidDelegate closure in closures)
{
closure();
}
Console.ReadLine();
}
}
If you're familiar with C# 2.0, you'll know that when run, this program will print out '10' for
ten lines. This is because there's only one instance of the variable 'i', and that instance is captured
by each delegate. Because the for loop changes the value of i, at the end of the loop every delegate
will have a capture of the same variable, whose value is 10. That's why each closure prints '10'.
Larry contrasts the above program with this, in Ruby:
closures = Array.new()
#Create a bunch of closures
10.times { | i |
myClosure = lambda {
#Capture outer variable
puts(i)
}
closures.push(myClosure)
}
closures.each { | myClosure |
myClosure.call()
}
This, of course, prints each number from 0 to 9.
Now, one might draw the conclusion from this that Ruby's closures are different from C#'s closures.
That wouldn't be the right conclusion though - Ruby's variable capture is actually no different from C#.
The difference is all down to the scope of the loop variable. I'll demonstrate it by writing cross-idiomatic
translations of the two programs - i.e. writing the Ruby program in C#, and the C# program in Ruby, trying
hard to preserve semantics.
First, I'll write the Ruby program in C#. The difference is down to the chosen iteration primitive: in Ruby,
it's a method of numbers which accepts a code block as an argument (at least, that's the primive Larry's chosen),
while in C#, it's the 'for' statement. So, to get the Ruby behaviour in C#, I'll have to write a Times method:
using System;
using System.Collections.Generic;
public static class App
{
delegate void Method();
public static void Times(int iterations, Action<int> action)
{
for (int i = 0; i < iterations; ++i)
action(i);
}
static void Main()
{
List<Method> methods = new List<Method>();
Times(10, delegate(int i)
{
methods.Add(delegate
{
Console.WriteLine(i);
});
});
foreach (Method m in methods)
m();
}
}
I can even use this as a gratuitous chance to use C# 3.0 LINQ Preview, with its extension methods and
more concise lambda expressions:
using System;
using System.Collections.Generic;
static class App
{
delegate void Method();
static void Times(this int iterations, Action<int> action)
{
for (int i = 0; i < iterations; ++i)
action(i);
}
static void Main()
{
List<Method> methods = new List<Method>();
10.Times(i =>
{
methods.Add(() => Console.WriteLine(i));
});
methods.ForEach(m => m());
}
}
Don't worry, I don't think it's much more readable either :) Anyway, both of these two C# programs,
following an emulation of a Ruby loop primitive, produce the numbers 0 to 9. That's because they
capture a new instance of the variable each time, in an argument.
Now for the Ruby version of the original C#. Here, the Ruby for loop over a range is sufficient to
demonstrate behaviour similar to C# (forgive my horrible Ruby, I'm only a beginner :):
closures = Array.new()
for i in 0..9
closures.push lambda { puts(i) }
end
closures.each { | myClosure |
myClosure.call()
}
This, similarly to the C# version, prints out '9' ten times, because it's captured the single instance
of 'i'.
I'll get even closer to the C# implementation. The C# for-loop, as in other C-based languages, is syntax
sugar for something like this:
for (<initialStatement>; <condition>; <loopExpression>)
<body>
// Translates to...
{
<initialStatement>;
while (<condition>)
{
{
<body>
}
<loopExpression>;
}
}
// ... with extra braces to reflect the C#/Java/C++ style scoping.
So, onwards to a Ruby transliteration of this (but not translating the scoping, which in Ruby appears
to be limited to code blocks (e.g. lambdas) and methods, not compound statements):
closures = Array.new()
i = 0
while i < 10
closures.push lambda { puts(i) }
i = i + 1
end
closures.each { | myClosure |
myClosure.call()
}
This prints '10' ten times.
So, there it is. Ruby's closures don't do any deeper copying of variables than C#, and with some of the looping
primitives, they're even susceptible to the same downsides. An advantage Ruby has is the way its
closures mesh well as arguments to methods, resulting in fairly clean language extensions. That means
that one need never directly use Ruby's 'for', 'while' or 'loop' primitives to implement loops - but
that's a matter of style.
It's interesting that Java's
proposed closure syntax
has this composability feature in common with Ruby.
No comments:
Post a Comment