Well, I’m glad to see that even with my writer’s block, people still seem to be reading the blog! Although there is definitely a diversity of opinion, the majority of people seem to prefer the “Function” syntax of the choices I laid out, which is not exactly what we expected. (We were wagering people would go for the more cryptic, compact syntax. Shows what we know…) That’s the syntax you should expect to see in the beta, and if public opinion shifts over time in the beta, we’ll deal with that feedback if and when we come to it. After all, that’s what a beta is for…
The other major topic to talk about with lambda expressions is closures. I don’t have a particular question this time, just an update on a topic we discussed about nine months ago. When I talked about closures back March, I raised the question of exactly how VB should treat local variables when lifting them into closures. I won’t rehash the entire discussion here–you can just go back and read the original entry itself–but the problem boiled down to something like this:
Module Module1 Delegate Function AddDelegate(ByVal x As Integer) As Integer Sub Main() Dim lambdas(9) As AddDelegate For i As Integer = 0 To 9 Dim y As Integer = i lambdas(i) = Function(x) x + y Next For Each l As AddDelegate In lambdas Console.WriteLine(l(0)) Next Console.ReadLine() End Sub End Module
This is the same example as the previous entry on closures, except that instead of using queries, I’m using lambda expressions directly. What I’m doing here is filling an array with lambda expressions that are supposed to add a particular value to the parameter. So lambdas(0) will add 0 to the parameter, lambdas(1) will add 1 to the parameter, etc. At least, that’s the intent. But now we run into the closure question that I asked originally–should each iteration get it’s own copy of the local variable y, or should they all share the same copy of y? If the former, I get the intended semantics. If the latter, then every lambda adds 9 to the parameter (because they share the same y and the final value of y is 9).
Just to make the problem clear, let’s look at an equally valid way (in VB) of writing the same code:
Module Module1 Delegate Function AddDelegate(ByVal x As Integer) As Integer Sub Main() Dim lambdas(9) As AddDelegate For i As Integer = 0 To 9 Dim y As Integer lambdas(i) = Function(x) x + y y += 1 Next For Each l As AddDelegate In lambdas Console.WriteLine(l(0)) Next Console.ReadLine() End Sub End Module
Now instead of initializing y at the beginning of each iteration of the loop, I’m just incrementing it and letting the value carry over from one iteration of the loop to another. Now maybe we start to see the problem–if each iteration of the loop gets its own copy of y, then that seems to conflict with the idea that the value of the variable carries over from one iteration of the loop to another. We’re trying to eat our cake and have it too.
What we ended up deciding was to split the difference. It’s a little more complex that what you get in C#, for example, but it should give everyone the semantics they expect and not break any existing code (always a plus, in my book). Basically, we are going to treat the lifetime of local variables to be the scope that they are declared in. Thus, when you create a closure in a loop, each iteration of the loop does, indeed, get its own copy of the local variable. But to preserve the existing semantics, we’re going to add a caveat: when creating a new local variable, if a previous version of that local variable exists, we copy the value from that previous version into the newly created local variable. So the value of a local variable declared in a loop carries over from one iteration to the next. So lambdas work the way most people expect, and existing code continues to run as expected.
As with previous entries on closures, kudos to those who’ve bothered to read this far. It’s kind of arcane and, we believe, most people won’t ever have to think at all about the special semantics–things will just work.