Both Delphi 2009, and the upcoming Delphi Prism have support for anonymous methods. Now while I am very excited about other features, notably generics, I just couldn’t get excited about anonymous methods. I just didn’t get them. I understood the syntax, but to me they looked like inline methods. My biggest question was why should I use an anonymous method, and not a procedural types, like we’ve always done in Delphi? Ok, I save having to write a separate method, but what’s wrong with separate. It’s probably more readable that way anyway.
I read blogs the blogs and articles. I studied examples, which always seemed to be artificial, or just too complicated to actually explain the concepts. I read the Wikipedia entry on closures, which are what everybody else seems to call them, but still no joy. All I really want is to be able to understand the concept, enough that during my everyday work, I can identify areas where an anonymous methods would fit nicely, and where they would be the best choice.
Being a recent convert to Stackoverflow.com, I asked a question there. Can someone explain Anonymous methods to me? I got quite a few answers, some with yet more code examples. I still couldn’t see how any of this was going to make my day to day coding any easier, or even a little bit more exciting! That is until I got one answer which made me go Aha. Actually it was one line within that answer.
Just think of typical callback code where you need to have data available to the callback.
So I looked at some code I’d recently written, which used a callback, and replaced that callback(literally a method pointer) with an anonymous method. The code I had recently written was an implementation of a Direct Acyclic Graph. Basically a way to represent a number of objects (vertices), and the relationships(edges) that exist between those objects. Think cities(the vertices) with possible flights between them(the edges). Then using certain well-defined algorithms, you can traverse those vertices in various ways. So going back to the cities and flights example, it would be possible to find the shortest route between any two cities for example.
I’d originally implemented these traversals using procedural types.
type
TTraversalMethod = procedure(v: TVertex);
TGraph = class(TObject)
private
public
procedure LinearTraversal(aMethod :
TTraversalMethod);
end;
procedure TGraph.LinearTraversal(aMethod :
TTraversalMethod);
var
v : TVertex;
begin
for v in Vertices do
aMethod(v);
end;
So basically LinearTraversal does nothing special except traverse all the vertices in a linear fashion. Not too exciting. So let’s say we now have a graph and we want to display the vertex names as a string, which we’ll implement using a TStringList;
procedure TMyClass.TraverseMethod(v : TVertex);
begin
fStringList.Add(V.Name);
end;
procedure TMyClass.ShowGraphAsText(aGraph : TGraph);
begin
fStringList.Clear;
aGraph.LinearTraversal(TraverseMethod);
ShowMessage(fStringList.Text);
end;
You’ll notice that we need two methods, one to actually prepare the call, and the other is the function called on each vertex. We also need a stringlist which must be visible from both methods. Unfortunately even if we make it private to the class, it’s still visible to all other methods in the class. What if some other method is expecting some other list to be stored in that stringlist? A year down the line how can we possibly remember not to touch that stringlist?
What would be ideal is if we could make the stringlist local to the ShowGraphAsText method. Well it turns out we can. I’m going to rewrite the above using anonymous methods. I’ll use Delphi Prism/Oxygene as that’s what I’ve got at hand, but Delphi 2009 would work just as well.
type
TTraversalMethod = public method(v : TVertex);
TGraph = public class
…
…
public
method LinearTraversal(aMethod : TTraversalMethod);
end;
method TGraph.LinearTraversal(aMethod : TTraversalMethod);
begin
for each v in Vertices do //the type of v is inferred
aMethod(v);
end;
The actual code for the traversal looks pretty similar. There are a few Delphi Prism constructs. method instead of procedure, and for each in instead of for in. Also note the type inference in the LinearTraversal method.
It’s the calling code that things get interesting.
method TMyClass.ShowGraphAsText(aGraph : TGraph);
begin
var s: StringBuilder := new StringBuilder(‘’);
Graph.LinearTraversal(
method(v : TVertex);
begin
s.Append(v.Name);
end
);
MessageBox.Show(s.ToString);
end;
We now only have one method (well strictly speaking two), and more importantly we have our local helper variable.
And that was my aha moment. It’s not the actual fact that you can inline an anonymous method where you need it that makes them so powerful, it’s the capture of local variables of the outer method inside the anonymous method. When you look at it like that, you see them in a whole new light. Now I can go back to all those examples I’ve seen and understand them better.
No comments:
Post a Comment