Closures… Implicit or Explicit ??

Posted Wed, Apr 5 2006 17:27 by bill
A recent discussion on Paul's blog about local variables scope and lifetime led the discussion to that of closures.  So maybe this is a good time to look at closures in general….  
 
Personally, I prefer to think of "closures" as being "state machines".  That is their primary purpose is to maintain state. Let's take an example using some theoretical syntax..
 
Sub Test()
   Dim count as Int32 =0
   Dim del as New SimpleDelegate(){count +=1 : Console.Writeline(count)}
   ' do other code here that might or might not change count
   ' invoke del
  del()
End sub
 
In the above code I've shown a theoretical anonymous delegate syntax using the count variable from the enclosing method.  So as the value of count is that of the local variable when the delegate is invoked, we have to change the reference to the local to that of a field.  So what happens is the compiler would create a class for you along the lines of
 
Private Class _ClassForMethodTest 'or some weird compiler generated name
 
  Public count as Int32
 
  Public Sub AnAnonDelegate()
      Me.count +=1
     Console.Writeline(Me.count)
   End Sub
End Class
 
now the method Test would be compiled as if the code was:
 
Sub Test()
   Dim c1 as New _ClassForMethodTest
   c1.count = 0
   Dim del as New SimpleDelegate(addressof c1.AnAnonDelegate)
   ' do other code here that might or might not change count
   ' invoke del
  del()
End sub
 
So we now have the state of the local variable persisting as long as the delegate is alive.  Note: a delegate keeps a reference to the object in which the delegate resides, hence keeping the object itself alive.
 
To understand why this is important we need to consider some of the different scenarios in which we might use this kind of code, a basic premise being that it will be with LINQ and DLINQ statements, anonymous methods and lambda expressions.
  • With LINQ, DLINQ, the combination of projections
  •   "      "         "     delayed evaluation
  • With a wide range of local variables including reference types, finalizable objects and disposable object
 
Take the last scenario for example, and substitute "count" with an object that is Disposable.  You wouldn't want the object being disposed of before the delegate is invoked as that would just end up throwing a null exception of some sort.  So there we do want the local variable to be kept alive.
We definitely wouldn't want a copy to be made, as that might end up in using excessive resources and actually break referential integrity.
 
Now imagine the scenario where we pass the anonymous delegate out to another object, and the local variable is indeed a reference type.  Copying would be out of the question, and yet we need the object lifetime to be that of the delegate.  Hence the "closure" provides a means of safely maintaining state.  The local variable reference needs to be stored in the closure for it's lifetime to be maintained as long as the anonymous delegate's lifetime.
 
This of course may not always be what you'd want.. For example
 
  Dim person as New Person("Fred")
  Dim del as New SimpleDelegate(){Console.WriteLine(person.Name)}
  person = New Person("Wilma")
  del()
 
The above would print out "Wilma" even though you might want it to print out "Fred".  This is basically where a lot of the comments on Paul's blog entry are currently centered.
 
Okay, so let's assume you want that to print out Fred.  How could that possibly happen ?  One way might be to force evaluation of the code inside the delegate there and then, but that's not always going to work as the delegate could have parameters passed to it, and you might need those parameters to retrieve the property. So we can't evaluate it there and then. What we end up having to do is in fact a closure, a class with a field that points to the person object.  But at this point we could take two different paths.  One would be to have person variable actually be c1.person, the other not.  So in either case we'd still have the same basic closure:
 
Private Class _ClassForMethodTest 'or some weird compiler generated name
 
  Public person as Person
 
  Public Sub AnAnonDelegate()
      Console.WriteLine(Me.person.Name)
   End Sub
End Class
 
The question is should the calling code be
 
  Dim c1 As New _ClassForMethodTest
  c1.person = New Person("Fred")
  Dim del as New SimpleDelegate(AddressOf c1.AnAnonDelegate)
  c1.person = New Person("Wilma")
  del()
 
hence printing out "Wilma", or :
 
  Dim c1 As New _ClassForMethodTest
  Dim person As New Person("Fred")
  c1.Person = person
  Dim del as New SimpleDelegate(AddressOf c1.AnAnonDelegate)
  person = New Person("Wilma")
  del()
 
hence printing out "Fred"
 
so the last sample, the one that prints out "Fred" de-couples the state.  In the example where state is maintained you could provide the same decoupling by introducing a new local variable:
 
  Dim person as New Person("Fred")
  Dim temp as Person = person
  Dim del as New SimpleDelegate(){Console.WriteLine(temp.Name)}
  person = New Person("Wilma")
  del()
 
that code would achieve the same, also printing out "Fred" even when compiled using a state maintaining closure.
 
But what if you wanted it to print out Wilma. Well in that case only the stateful approach would work. You could of course write your own closure to achieve the same, but then you'd loose the anonymous delegate encapsulation. With LINQ code it'd get incredibly frustrating and complex to write a stateful query.
 
So if we didn't have closures maintaining state we'd need a simpler syntax to  indicate that we wanted state maintained.  the inverse however is not necessarily true, as we can use temporary local variable to decouple state.  So if we were to say no new special keywords, then I think the argument for closures being stateful by default pretty much wins out.
 
If however we say we'll use a keyword then we have two options. One to indicate a closure is stateful, or one to indicate it is not.  To indicate stateful, I think something like "ref" or " ByRef" could be used.  To indicate decoupling, perhaps something like "Eval"  or ValueOf or "ByVal" could be used.
 
Although I don't like them as much as the other  alternatives I've listed, "ByRef" and "ByVal" would have the least impact on any existing code as they already have special keyword status.
 
This possibly leaves the door open to somewhat spurious arguments such as ByVal is the default for parameters so should be for all code in anonymous delegates or lambda expressions.
 
Perhaps some examples are needed here:
 
  Dim person as New Person("Fred")
  Dim temp as Person = person
  Dim del as New SimpleDelegate(){Console.WriteLine(ByVal(person).Name)}
  person = New Person("Wilma")
  del()
 
and :
 
  Dim person as New Person("Fred")
  Dim temp as Person = person
  Dim del as New SimpleDelegate(){Console.WriteLine(ByRef(person).Name)}
  person = New Person("Wilma")
  del()
 
would print out Fred in the first case and Wilma in the second.
 
This probably raises yet another question, should the ByVal|ByRef be required to be explicit in all cases where a closure is created with that variable ? 
 
I could see ByVal being handy in places just like how we use to use ()'s in Vb6 and earlier. (note I would NOT want to see the return of that behavior by using ()'s alone)  I could also see using ByVal would be handy when passing a Property into a method where the parameter is ByRef.
 
One of my biggest concerns would be is if ByVal was the default behavior and the ByVal optional or omitted.  you could then have seemingly identical code in C# which was very different from VB code.  That's not a desirable thing.  it obviously shouldn't be the driving force, but it's yet another thing to consider.
 
I think my vote is for the default behavior to be ByRef, and that you could optionally use ByVal to decouple. That is ByRef would be implicit. All in all, I think that would be the most desirable behavior.  If ByVal was the default, I think I would like to see it be explicit, even if it means the code editor would add it for you.
 
 
 
 
Filed under: , ,

Comments

# re: Closures… Implicit or Explicit ??

Wednesday, April 05, 2006 3:21 PM by Jonathan Allen

I'm leaning in your direction, but you haven't convienced me yet. Can you give some more examples of when the "ByRef" behaviour is desirable?

# re: Closures… Implicit or Explicit ??

Wednesday, April 05, 2006 6:42 PM by bill

Jonathan, I think it really depends on how you view anonymous delegates and lambda expressions. If you view them as just inline function calls, then you don't get the beauty and power of them as re-usable code blocks. Perhaps the easiest way is for people to think of them as nested procedures.

I think as soon as you start doing anything with them other than very simple statements, you do expect them to be more "ByRef". Let's expand the sample given above:

Dim person As Person

Dim del as New SimpleDelegate(){Console.WriteLine(person.Name)}

person = New Person("Fred")
del()
person = New Person("Wilma")
del()
person = New Person("Barney")
del()
person = New Person("Betty")
del()

Now would you expect that to print out "Fred", "Fred", "Fred", "Fred", or the cast of characters ?

# re: Closures… Implicit or Explicit ??

Thursday, April 06, 2006 10:39 AM by Jonathan Allen

person = New Person("Fred")
del()
person = New Person("Wilma")
del()

There is no indication that the result of the function del is in anyway related to changing the value of person. As such, I would consider using the anonymous delegate in this fashion to be improper.

Can you give me an example of who you would use it in a real project?

# re: Closures… Implicit or Explicit ??

Thursday, April 06, 2006 7:25 PM by bill

the declaration of del defines it's relationship. That is no different for any method or delegate.

You need to see anonymous delegates like nested procedures to see the forest.

Let me dilute the example a bit and change it to a class.....

Class MyClass

Private person As Person

Sub PrintReport()
Console.WriteLine(person.Name)
End sub

Sub Test()
person = New Person("Fred")
PrintReport()
person = New Person("Wilma")
PrintReport()
End sub

End Class

So calling the Test method inside an instance of MyClass would print out Fred and Wilma. That is the variables of the container are shared amongst the procedures by reference.

Now picture anonymous delegates as nested procedures. Their container is the procedure in which they are defined. It's like fractals I suppose, we are just moving the level of encapsulation one level finer.





# re: Closures… Implicit or Explicit ??

Friday, April 07, 2006 12:50 AM by Jonathan Allen

Ok, I buy the nested functions argument.

In the terms of LINQ, what do we gain by having the "ByRef" behaviour?

# re: Closures… Implicit or Explicit ??

Friday, April 07, 2006 2:24 AM by bill

I don't understand your question. LINQ statements can contain lambda expressions and anonymous delegates. Surely you aren't suggesting the behaviour of a nested function be different when used in a LINQ statement compared to the surrounding code ?

# re: Closures… Implicit or Explicit ??

Friday, April 07, 2006 10:12 PM by Jonathan Allen

No, that would be silly.

I am under the impression that lambda expressions and anonymous delegates are essential for LINQ, but I don't know why. Nor do I know whether (or should I say when) ByVal or ByRef behaviour is more apropriate.

# re: Closures… Implicit or Explicit ??

Saturday, April 08, 2006 2:33 AM by bill

Oh, okay. Have you downloaded the VB LINQ preview bits ? That's probably the best way to get an understanding of the "why"'s in relation to query comprehensions and lambda expressions. Perhaps the following might help to explain:

consider a projection where you want to slect a group of people based on some of their attributes. Purely theoretical, but lets assume they have a DateOfBirth property, and you want ot get those people's who birthday it is for a given day. So you write the projection, and you feed it the date you are interested in when you iterate the projection.
This lets you re-use the same projection.

Now as part of that query comprehension that gives you the projection, you need lambda expression to deal with getting the person's DOB, then comparing the month and day values against the given date. Obviously that can't occur at compile time. So the vlaues for the expression are dynamic, they are for each person.





# re: Closures… Implicit or Explicit ??

Saturday, April 08, 2006 5:57 PM by Jonathan Allen

> Have you downloaded the VB LINQ preview bits ?

Yep, but I haven't had time to properly explore them.

Thanks for the info, it makes sense now.

> I could see ByVal being handy in places just like how we use to use ()'s in Vb6 and earlier. (note I would NOT want to see the return of that behavior by using ()'s alone) I could also see using ByVal would be handy when passing a Property into a method where the parameter is ByRef.

According to the docs, (var) is still the way to force it to be ByVal. I don't like that. I would perfer it to be...

foo( byval x)
or
Dim del as New SimpleDelegate(){Console.WriteLine(ByVal person.Name)}

But upon looking at it, I don't think that will work for the delegate. There is no way to determine if person or person.Name is being passed by value. I guess ByVal(person).Name and ByVal(Person.Name) will have to be used.

Ok, I think I agree with your main points. I would like to say we only need the ByVal keyword, but we might as well have both to make it easier on people reading the code.

For the default case (I assume it would be ByRef), should that be explicit? In other words, should the IDE automatically add ByRef?

I'm leaning towards no, as it may clutter the code too much.

# closures continued.. ByRef, ByVal and ()

Saturday, April 08, 2006 9:35 PM by @ Head

continuing the discussion on closures….
 
A rarely used, somewhat obscure feature of VB, is by enclosing...

# My and threading (TheadStatic, ThreadSafeObjectProvider)

Friday, April 14, 2006 1:50 AM by @ Head

Yesterday I got an interesting email from Tobin Titus about an issue with threading and My.Forms, so...

# My and threading (TheadStatic, ThreadSafeObjectProvider)

Friday, April 14, 2006 1:59 AM by @ Head

Yesterday I got an interesting email from Tobin Titus about an issue with threading and My.Forms, so...

# Closures in VB.Net

Saturday, January 13, 2007 2:23 PM by Ayende @ Blog

Closures in VB.Net