Category Archives: Visual Basic

Local variables: scope vs. lifetime (plus closures)

Over a month ago, I asked what a particular chunk of code should do:

Module Module1

    Sub Main()

        For i As Integer = 0 To 2

            Dim x As Integer

            Console.WriteLine(x)

            x += 1

        Next

        Console.ReadLine()

    End Sub

End Module

I purposefully left the question open and vague because I wanted to see what the community feedback would be without any kind of preconceived notions. I didn’t expect for it to take me so long to return to this question, so I apologize if people got frustrated waiting, but I do want to get back to why I asked and what I think about the whole question. Let’s start by getting what actually happens out of the way: the program prints “0 1 2”. The reason for this takes a little bit of explaining.

What’s important here are two related but different ideas: scope and lifetime. The scope of a variable decides where a variable’s name can be used in a program. The lifetime of a variable decides how long the storage for that variable exists in memory. (In most programming languages the scope of a local variable is at very least a subset of the lifetime of the variable, otherwise you’d be able to refer to the local variable after its storage goes away, which would be bad.)

So the question now boils down to: what’s the lifetime of a local variable in VB? Most people who assumed that the answer would be “0 0 0” made the reasonable assumption that the lifetime of a local variable is the same as the scope of the local variable. So they expected that when the code reached the end of the For…Next block, they’d reached the end of both the scope and the lifetime of the local variable x and the storage for x would go away. Then, when the loop started up again, we would give you a whole new storage location for x that (like all storage locations) was initialized to zero.

However, those of you who tried it out discovered that in VB the lifetime of a local variable does not equal its scope. In fact, the lifetime of a local variable is from the beginning of a method all the way through to the end of a method, regardless of the variable’s scope. Even though x is only in scope within the For…Next loop’s statement block, it lives throughout the entire method. Thus, when you loop, you get the same storage location as you got the last time. And thus you get “0 1 2” instead of “0 0 0”. And, in fact, this is consistent with the way the Common Language Runtime works. When you define a method, you declare the locals that the method is going to use. When you enter the function, the CLR creates storage for those local variables and initializes them to zero. And when you exit the function, the CLR throws away the storage for those local variables. So VB is actually entirely in sync with what it’s platform does. And it’s the same for C#, only they finesse the issue — since you have to explicitly initialize all locals in C#, there’s no way to observe whether the lifetime of a local variable extends beyond it’s scope. But their local variables live just as long as the ones in VB.

This whole discussion is something of a minor point, at least until you get to closures, that is. What are closures, you ask? Well, the best way to explain them is by example. Let’s say you’ve got code that looks like this:

Sub Main()

    Dim value As Integer

    Dim xs = { 1, 2, 3, 4 }

 

    value = 2

    Dim ys = Select x From x In xs Where x < value

 

    For Each y As Integer In ys

        Console.WriteLine(y)

    Next

    Console.ReadLine()

End Sub

You’ll notice here that the query references the local variable “value”. Those of you well versed in the intricacies of LINQ will know, however, that the way LINQ works is that it pulls the expression “x < value” off into a function, a delegate of which gets passed to the Where method. Then the Where method uses this delegate to determine which members of the xs collection are filtered out. But how can we pull out the expression “x < value” to another method when the expression refers to a local variable? One method can’t see another method’s locals! Or can it…?

What happens in this case is we use a closure. A closure is just a special structure that lives outside of the method which contains the local variables that need to be referred to by other methods. When a query refers to a local variable (or parameter), that variable is captured by the closure and all references to the variable are redirected to the closure. So the statement “value = 2” assigns the value 2 to a variable location in a closure, not a variable location on the stack. Since the closure lives outside of the method, methods created by a LINQ query can legally refer to the local variables captured in the closure. And it all just works.

I’m purposefully skipping over a lot of the nitty-gritty of how closures work to avoid writing a whole chapter on this subject, but the practical upshot of this is that with closures, the lifetime of a local in an inner block becomes a whole lot more important. Let’s go back to a modified version of our original code:

Module Module1

    Sub Main()

        Di
m
queries(2) As IEnumerable(Of Integer)

        Dim xs = { -2, -1, 0, 1, 2 }

 

        For i As Integer = 0 To 2

            Dim y As Integer = i

            queries(i) = Select x From x In xs Where x <= y

        Next

 

        For Each q As Integer In queries(0)

            Console.WriteLine(q)

        Next

        Console.ReadLine()

    End Sub

End Module

The intent of this code is to create an array of queries that have different upper bounds — so queries(2) will return all values less than or equal to 2, queries(1) will return all values less than or equal to 1, and queries(0) will return all values less than or equal to zero. At least, that’s the intent. But if you go try this on the current LINQ code on my machine (not sure if it’ll run on the latest CTP or not), you’ll actually get the following result: “-2 -1 0 1 2”. Huh? The problem is that, if you’ll remember, the variable y lives for the entire method. Each iteration of the loop doesn’t get its own copy of y, it gets the same copy of y that every other iteration gets. This means, though, that when the query captures the local variable y, each iteration of the loop captures the same copy of y. Which means that when y gets changed inside of the loop, all the queries’ copy of y gets changed. All of the queries are going to return the same set of values.

What you really want in this case is for each iteration of the loop to capture a unique copy of y. In other words, you want to treat y as if its lifetime was only the inner part of the loop, not the whole method. And if you look at what C# does with anonymous delegates (and, now, lambda expressions), you’ll see this is what they do — since they require definite assignment, they can behave “as if” variables in inner scopes have shorter lifetimes than the entire method (even though they really don’t). To accomplish this, they have to use nested closures, which is beyond the scope of this entry and is left as an exercise to the reader (for the moment, at least).

So, the practical upshot is that with the introduction of closures to VB (regardless of whether we expose lambda expressions, which is still a bit of an open question), we’ve got a problem with local variable lifetime. We could use our flow analysis, introduced in VB 2005 for warnings, to perhaps finesse this issue the way C# does, but there are some complications. It’s very much an open issue, which is why I really wanted to see what people’s expectations were — it’s really useful data for understanding how people (at least those who read my blog) think about the problem.

Expect more down the road once we’ve got more of a handle on the problem, and kudos to anyone who made it this far

Updated 3/29/06: Corrected code error!

New Sample: VBParser 8.0

OK, it took a while, but my updated version of the VBParser source code sample is finally up! Since the 7.1 version of VBParser ended up being essentially a read-only project on GotDotNet, I’ve decided to eschew GDN and just go with a straight source download. I’ve added a new set of links on the left hand side of my blog entitled “Samples,” and put both the 7.1 and 8.0 version of VBParser there.

Things that are new or changed in VBParser 8.0:

  • I’ve updated the source to take advantage of VB 8.0 features such as generics, IsNot, etc. Because of this, VBParser 8.0 is not API compatible with VBParser 7.1. Any code that’s built with VBParser 7.1 will need to be updated, but it shouldn’t be too painful of a task.
  • As the name implies, VBParser 8.0 should parse all of the new VB 8.0 features. There is also a 7.1 compatibility mode that you can use to parse VB 7.1 code (which is a strict subset of VB 8.0).
  • This is a sample that I wrote on my own, which means that the only testing that it got was the testing that I did on my own. As such, there are no guarantees made of completeness, correctness or suitability for any particular purpose. I did try my best to make sure that it works well, but you should consider the sample as being in permanent beta. There may be stupid bugs still lurking in the source.
  • Please drop me a line with any and all bugs that you find and I’ll be happy to take a look at them and fix them as time permits. If you end up using VBParser for something, let me know!

You can get VBParser 8.0 here. [07/12/2014: The sources for VBParser 8.0 are now on GitHub.] You can get VBParser 7.1 (the previous version) here. [07/12/2014: The sources for VBParser 7.1 are now on GitHub.] Hope people find it useful!

I haven’t gone AWOL…

…life just went and got very complicated for a while. In particular:

  • My wife Andrea has had some strange medical symptoms over the past few months that our neurologist thought indicated pretty strongly she has MS. So the past month has been spent getting MRIs and lumbar punctures to try and narrow down the diagnosis. The good news? It’s pretty unlikely she has MS or any of the related types of diseases (RA, Lupus, etc.). The bad news? Still not sure what’s going on. Still, since none of the symptoms are extremely serious, I’ll take “don’t know” for now, given the alternatives.
  • We’re pushing to get another LINQ preview out in the near future and I’ve been on the hook to write a number of new features for the preview. It’s been great to get the chance to write a lot of code again, but since that’s not my day job any more, it has contributed to a real time crunch.
  • The whole question of scripting languages, dynamic languages, dynamic environments, loose typing, etc, etc, etc. which has been bubbling around in the background for well over two years seems, all of a sudden, to be coming to quite a boil. Nothing in particular to talk about yet, but lots of interesting and exciting stuff bouncing around and perhaps some quite interesting things to talk about in the near future.

Anyway, I think I should be back now, barring any unexpected surprises. Time to get back to that question of local variables, eh?

Just because it comes in your size, doesn’t mean you should wear it.

I found Guido van Rossum’s meditation on Language Design Is Not Just Solving Puzzles quite on target and relevant to what we’re going through right now as we consider how to design the myriad of language features that make up LINQ. (Thanks to Lambda the Ultimate for the pointer.) If you look at all the possible kinds of language features that you can stick into a programming language, it often seems like there’s an inexhaustible supply of them and many of them are quite interesting or compelling. But the problem is that, in the end, a programming language has to have some kind of semi-coherent style or design vision associated with it or else it starts to collapse into an unintelligible mess.

This fact often puts extremely annoying constraints on what you can do when designing new language features. The example we’re struggling with right now is lambda functions. The whole point of a lambda function is to create a really terse, inline function, but Visual Basic is, by nature, a wordy language. Which means that most of the “obvious” designs for a lambda expression involve something that looks worse than not using lambda expressions at all. Multi-statement lambdas are also a problem due to our line-oriented nature (and, ironically, this is what motivated Guido’s entry as well). What’s the answer? Right now, it’s not clear. But when you get boxed in like this, sometimes you have to sacrifice functionality you’d really like to have but just can’t find any particularly good way to express. Some things just can’t be done within the overall framework of past design choices. It’s just the way it is.

(The title of this entry is something my wife likes to say when confronted with someone wearing something clearly not designed for their body shape (e.g. someone with a pronounced gut wearing a belly shirt). Just because you can wear something doesn’t mean that you should.)

 

What should this code do?

OK, I’ve got a question for y’all. Take a look at the code sample at the end of this entry and leave me a comment on what you think the output of the program should be. Note that I’m looking for people’s opinions here and this isn’t one of those “how well do you know VB” trick questions. Once you’ve put in a comment on what you think the output should be, then feel free to run it and see what it does, but do hold off commenting on the actual behavior until people have had a chance to comment. Once we’ve got some comments, I’ll write a longer entry on why I’m asking this and why the answer matters at this particular moment in time.

Module Module1

    Sub Main()

        For i As Integer = 0 To 2

            Dim x As Integer

            Console.WriteLine(x)

            x += 1

        Next

        Console.ReadLine()

    End Sub

End Module

Updated VB 9.0 (LINQ + XML) preview out!

I know this is old news, but I’ll say it anyway: several weeks ago, we released an updated preview of our proposed 9.0 features. The preview is enhanced in four primary ways:

  • We now support some Intellisense for Select expressions. This is a step forward in our investigation of the Select/From vs. From/Select question, so we’re definitely interested in feedback here.
  • We now support a lot more LINQ, specifically DLinq and variable capture (so you can now access local variables in queries). A huge chunk of my November/December went towards implementing lambda expressions and expression trees in Visual Basic and that, combined with some excellent work by another team member on variable capture, means a lot of stuff works now that didn’t before.
  • The editing/display experience for XML literals has been greatly enhanced — just having colorization makes a huge difference.
  • A bunch of extensions were made to the XLinq support to make working with namespaces and elements easier.

Amanda covers this in more detail in her entry, but this should give you a flavor. Eagle eyed readers will note that I said that I did a lot of work on “implementing lambda expressions,” but it’s important to realize that at this point lambda expressions are only used as a part of query comprehensions — there is no explicit syntax for lambda expressions yet. The priority was on getting the DLinq support working, and now that we’ve got that, we’re moving on to flushing out some of the remaining questions like lambdas…

Hope you enjoy!

Update 02/08/2006: I also forgot to mention that Amanda and I did a MSDN TV episode talking a bit about the new CTP.

Language Design & Paying the UI “Tax”

As Raymond says, most development teams face some kind of “tax” in their day-to-day work. Taxes are things that a programmer has to do or take into consideration that are usually: a) not specifically assigned for him to do, b) not specifically accounted for in his schedule, and c) not specifically very much fun. The first example Raymond used was power management — something that pretty much every Windows application should take into account but which a very great many applications don’t.

One of the wonderful things about working on a compiler is that, by and large, you escape most of the more egregious taxes. You don’t have any UI, you are pretty task-focused, you don’t deal with many other components in the system, and everyone expects you to consume a lot of resources (although not so many that the machine is brought to its knees). So lots and lots of things that other programmers have to worry about, we don’t. It’s great!

Wait, though, did I say that we didn’t have any UI? Well, technically, that’s true — the core compiler group takes text in and outputs bits on disk. But while the compiler doesn’t really have a UI itself, we don’t get off the hook completely because there’s lots and lots of UI that gets built around the compiler to help people, you know, write programs and stuff. And the language and compiler design cannot be entirely ignorant of that fact, nor can it bear no responsibility for how easy or hard it is to write that UI.

We’re at the point now with LINQ where we’re really starting to take a serious look beyond the language features, cool as they are, and pondering how these features are going to integrate with the UI. (It’s not like we haven’t thought about it at all, we have, but now we’re really taking a look at things end-to-end.) And so we started by coming up with a “language UI taxes” list that I’m shocked we haven’t put into a more concrete form until now. The various taxes that a language feature is going to have to pay include, but are not limited to:

  • The code model
  • Edit and Continue
  • Stepping in the debugger
  • Expression evaluation in the debugger (for things like the watch window, immediate window, etc.)
  • CodeDOM
  • Intellisense
  • Object Browser/Goto Definition
  • Class View
  • Code spit (i.e. things like automatically pasted in overrides and event handlers)
  • Tooltips
  • Rename symbol
  • Error correction
  • The dropdowns at the top of the editor window
  • The attribute editor
  • Dynamic help
  • Pretty listing
  • Code colorization
  • Editor outlining
  • Refactoring
  • Snippets
  • XML Documentation
  • Class Designer

In many ways, the real cost of a language feature isn’t in the feature itself, it’s in the myriad of UI taxes that must be paid on any particular feature. Optimal tax planning is the secret to a good schedule these days…

Billy Hollis, amateur PL historian…

This has been bouncing around a bit on blogs, but I thought it was funny… Billy Hollis, a longtime VBer, has posted some of his historical ruminations on VB and C-style languages:

If you like VB, look at the history of the C family [of languages] first. If you like C#, Java or C++, look at the history of the BASIC family first.

But who’s got the Star Trek game? I loved that game…

More samples coming soon (I hope)…

Just a FYI, I’ve changed the title of the “VBParser” category to “Samples,” in preparation for what I hope are several new samples in the coming months. The main thing I’ve been working on so far is an update of the VBParser sample that I wrote for VB 7.1. Besides updating the parser to parse VB 8.0 language features, I’ve also changed it to use nifty CLR 2.0 features like generics. My hope is to have it out in the next few weeks. Unlike last time, I don’t think I’m going to use GotDotNet as a distribution medium — since no one added anything to the project, I’m just going to distribute it directly and let people mail me bugs and such if they wish. Beyond the parser, I’m thinking about some samples that might fit in with LINQ, but we’ll just have to see…