Understanding Anonymous Methods

Posted by on in Blogs
It appears that the next version of Delphi will support a feature called anonymous methods.  The circumstances in which one might use an anonymous method do not seem to be obvious to everyone in the Delphi community, so I'm going to attempt to answer that.

There is, at the moment, no shipping version of Delphi which includes anonymous methods, so I'm basing my discussion on my experiences using them in other environments.  The final, released version may include a different feature set and what I describe below.

First, let's answer the obvious question, with the obvious answer: What can I do with an anonymous method which I can't do without an anonymous method?  Nothing.  In fact, there's nothing which you can do with Delphi which you cannot do with ASM.  Anonymous methods are a convenience suited to certain styles of programming, like strong, static typing, and object orientation.  They will probably seem useless to people who do not adopt those styles of programming, just as virtual methods will seem useless or dangerous to a non-OO programmer.

I suspect that most "Pascal programmers" view procedures as being a fundamentally different type than other data structures.  In the static typing the world, we typically define the shape of a data structure at compile time, and the contents at runtime.  By contrast, even when passing a procedure as an argument to another procedure, in older versions of Delphi we have had to define both the shape and the contents of the procedure at compile time.  Only the arguments can be set at runtime.

With one exception: Nested procedures.  Let's talk about that, for a moment.

Nested procedures, scope, and variable capture

Nested procedures are procedures declared inside of another procedure.  This essentially reduces the scope of the nested procedure to being callable within the nesting procedure only: Here is a simple example.

procedure MyClass.Foo;
i: integer;
procedure Bar;
i := 1;

procedure MyClass.Other;
Bar; // compiler error

The output of calling Foo will be the following lines, written to the console:


There are two things to note here. First, note that Bar can "see" the variable i, which is declared as a member of Foo.  Outside of the Delphi community, this feature is often called variable capture, although for some reason that term doesn't seem to be common within the Delphi community.  But that's what it is.  Second, note that the scope of Bar is limited to the Foo procedure.  That's nice, from a modularity point of view, but it's absolutely critical when combined with variable capture.  Outside of the scope of Foo, the variable i has no context/value.

Even the inside of the Foo procedure, however, you cannot assign Bar to an event property or any other procedure reference.  The way that variable capture is handled internally makes this impossible, without some hacking.

Now, one might reasonably ask what the value of that variable capture feature is.  After all, in the example above, I could just as easily have passed i to Bar as a var argument, and the result would have been the same, albeit at the expense of a bit more typing.  Indeed, in the case of nested procs, variable capture is really little more than a labor saving device.

Anonymous methods

Returning to anonymous methods, let's start with the definition.  An anonymous method, as the phrase implies, is a method without a name.  In addition to being nameless, anonymous methods, in most environments, include some form of variable capture.

According to the Delphi roadmap, anonymous methods will be assignable to procedure references.  You can see an example of this on Andreano Lanusse's blog.

The combination of assignability and variable capture is quite powerful, because it effectively means that I can change the signature of the procedure reference type in the definition of the anonymous method.  That is, I can pass values which are not included in the arguments declared in the procedure reference declaration.  That's unnecessary and perhaps even confusing if I've written the "reference to procedure" declaration myself.  In this case, I should just include the necessary arguments.  If, however, the declaration was written by someone else — say, the author of a framework — it's incredibly convenient, as I can now pass values which the framework author hasn't included on my behalf.

In the past, I've had to use some fairly ugly kludges to get around this limitation, such as declaring a new class field to hold some value which I only wanted to use inside of an event handler whose signature I did not control.  With variable capture, these kludges are unnecessary.

This returns us to the issue of "programming worldview" to which I alluded at the beginning of this post.  To a programmer unaccustomed to thinking of functions as values, with functions living in one little container, and data structures living in another little container, this may all seem a bit odd.  But if you consider a function as a bit of data, defining both behaviors and passed values, it's not so strange.  Of course we can define data at runtime, just like we've always done.
Tags: Delphi
Comments are not available for public users. Please login first to view / add comments.