Delphi Programming

and software in general.

Thursday, July 31, 2008

Anonymous methods - Variable Scope?

I am reading about all the Tiburón goodies with great pleasure, but I find myself uncertain of how some aspects of anonymous methods actually will work.

What variables that are in scope at the point of definition will be allowed used within the anonymous method, and which references should be avoided?

Can the method be defined without being assigned to a local variable, ie directly as a parameter to a call?

With reference to the articles on the TGridViewController, would it be possible to convert this (class method pointer)...

procedure TViewDirectory.DefineAttributes;
CreateTextColumn('Name', GetFileName, 300);
CreateTimeColumn('Date', GetFileDate, 100, taCenter);
CreateIntegerColumn('Size', GetFileSize, 100, taRightJustify);

// Note that Dir is a protected class property of TViewDirectory

function TViewDirectory.GetFileDate(const Row:Integer):TDateTime;
Result := FileDateToDateTime(Dir.Entry[Row].Time);

function TViewDirectory.GetFileName(const Row:Integer):String;
Result := Dir.Entry[Row].Name;

function TViewDirectory.GetFileSize(const Row:Integer):Integer;
Result := Dir.Entry[Row].Size;
to this? I.e. in-line declared anonymous methods

procedure TViewDirectory.DefineAttributes;
CreateTextColumn(function (const row:integer):String;
Result := Dir.Entry[row].Name;
end, 'Name', 300);

CreateTimeColumn(function (const row:integer):TDateTime;
Result := FileDateToDateTime(Dir.Entry[row].Time);
end, 'Time', 100, taCenter);

CreateIntegerColumn(function (const row:integer):Integer
Result := Dir.Entry[row].Size;
end, 'Size', 100, taRightJustify);
Apart from in-parameter definition (and any related syntactical mistakes I might have made), the problem here would be the reference to Dir. Since Dir is a class property (or private variable), it seems possible that this would be quite safe.

What about local variables?
Is this legal?
procedure DefineAttributes;
Reg : TRegistry;
Key, Value : TStringList;
Reg := TRegistry.Create;
key := TStringList.Create;
Value := TStringList.Create;

// we have ensured that GetRowCount returns the correct count

CreateTextColumn( function (const row:integer):string;
Result := Key[row];
end, 'Key');

CreateTextColumn( function (const row:integer):string;
Result := Value[Row];
end, 'Value');

I am really looking forward to learn more on this!


  1. Ahem... I pruned a little too much code from the first snippet. FYI: Dir is a proteced class property (a descendant from TStringList) for the TViewDirectory

  2. see the comment here (from the horse's mouth Barry Kelly, the "father" of closures in Delphi) ...

  3. I'm not sure that Barry's clarifications cover this, or at least a related question that I have.

    Barry mentions the capture of an implicit self.

    So if I have a closure that includes (but does not consist solely of):

    someVar := [self].SomeProperty;

    Is the value of SomeProperty captured or just the [implicit] self?

    Does it make a difference if SomeProperty has a reader fn, rather than just directly exposing a member var? What if the reader fn is virtual. Does THAT make a difference?

    These sorts of questions (and the likely complexity of the rules to be applied in any answers) are the things that to my mind will make use of this feature an abomination in terms of maintenance of any non-trivial code (and the benefits in the case of trivial code are pretty much non-existent).

    Which is why I'm still waiting for some promised concrete examples (by which I mean demonstration of use "in the wild", not isolated examples of the individual rules and specifications of the feature).

    If those concrete examples are readily understood without having to embed DCC32 in my brain then all good. If the examples need lengthy and involved explanation of what closures are formed under what conditions, then I shall be forced to accept that this feature is academically interesting but practically useless.

    It will be the new "with".

    People will love it for it's "time saving" but sensible developers will stay well clear.

  4. @Joylon - locations are captured; if you reference something implicitly via Self, you capture the Self hidden parameter. The rules are pretty simple, actually - I think it's one of these things that only seems complicated when it's new, but when you understand it, most of it boils away.

    The time saving is a game changer, by the way, for scenarios like parallel-for loops, UI/background thread communication, etc. And there are other scenarios where the abstraction makes a lot of sense - I have yet to write up much though.

    @Lars (original post) - yes, it is legal to use local variables - however be aware that any locals used by an anonymous method will have its lifetime extended to live as long as the anonymous method itself. So, if the function CreateTextColumn keeps the passed-in method reference (containing the anonymous method) alive after it has returned, then the key local variable may be freed prematurely.

  5. I get the capture of implied self - what isn't clear is what is captured that may be qualified by an implied self:

    [self.]SomeMember [read fSomeMember]
    [self.]SomeMember [read get_SomeMember]

    My gut feeling is that fSomeMember would (and SHOULD) be captured but that an implicit call to get_SomeMember would not (for to capture it would require invoking the reader fn entirely out of sequence which could not possibly be reliably safe).

    But it isn't at all clear to me whether a straight member "read" property would be captured. It could be argued either way.

    In fact, I can see a real tension here....

    In .NET - aiui - there is no such thing as a directly read property - there has to be a getter fn(). So in .NET if a property with a read fn is not captured then a property that does NOT have a read fn also cannot be captured.

    But in Win32 a property with no read fn IS a direct read of the member var and so if a member var ref would be captured, then a property ref that is a direct read SHOULD - intuitively - also be captured.

    Or maybe this confusion is founded on an utterly wrong premise.

    i.e. maybe


    Captures ONLY self, but not fSomeMember, but if so, that itself is confusing.

    And of course, once you understand something it is no longer complicated.

    Rocket science isn't hard - for rocket scientistists.

    But I consider myself (and though I say so myself I think considered by my colleagues to be) an intelligent and experienced developer with a good grasp of practicalities and "neat" language capabilities.

    It shouldn't BE this hard to grasp something that is supposed to be as obviously beneficial as this supposedly is.

  6. @Joylon - only locals and parameters are eligible for capture. fSomeMember is accessible once you've captured Self; and fSomeMember can't be captured (i.e. hoisted into a different class) because that would break the class that the field is contained in, much less the property declaration.

    Capture means grabbing a local or parameter and moving it into a heap-allocated location, rather than leaving it on the stack. It's that moving operation that lets the location live for as long as the last anonymous method that captured it lives.

    Things that are already on the heap, such as fields, don't need to be captured. All an anonymous method needs in that case is a reference to the instance, and it can get to the field just fine.

    And for what it's worth, there are a lot of newcomers to .NET who get confused about the difference between reference and value types - I think this confusion is on that level.

  7. @Barry - Thanks for clearing up on the local variable confusion. CreateTextColumn does indeed keep the method alive, which means (for this kind of scenario) that any content I want to access should be kept in the class, and not as an ephemeral local variable. Not really a problem as that is the natural way of doing it.

    I have to admit that I did wonder if (and even perhaps hope) the finally section would be grabbed and deferred until the last anon.method had been discarded, but it is obvious that the implications for the "local" code path would be a mess.

    I recognize that this is a very different scenario, f.x. compared to a parallelization or other lambda oriented use where the anon.method is not "kept alive" beyond the "lifetime" of the current method.

    I assume there will be ample compiler hints and warnings about the risks and perils with regards to referenced variable scope?

    @Jolyon - I'm wondering if understanding anon.methods are a bit like the transition from structured coding to OOP. Initially, we get the technical aspect of it, but we don't fully realize the implications and possibilities. The first months of "OOP" development I did, I was basically creating objects to organize structured code, until one day a light of almost solar proportion went up over my head. It dramatically changed my way of designing code.

    I am sure anon.methods can be abused as much as "with", but even "with" and "goto" have their occasional points of usefulness.

    I am very much looking forward to get my hands dirty with this.

    *Falls to his knees* For the love of virtual constructors! Someone, send me a beta! :)

  8. @Jolyon: Oh and one more thing - I hope I got this right now, or there will be egg on me face ^^

    Implicit self is sort of like passing Self (which is the reference to the current instance of the class you are in) as an argument to the anon.method. No need to worry about the property readers, and not really any difference from other self references.

  9. Unless... you are in an anon.method in a GetProperty, and happens to reference that property again... Hello circular, but that would have happened in a regular scenario as well. No new mistakes to learn from that.

  10. "No new mistakes to learn from that."

    But, unless I'm very much mistaken, a darn sight harder to spot and realise your mistake.

    As for realising the implications and possibilities, when OO was described, the implications and possibilities were also EASILY described in a way that made the brand new and unfamiliar OO immediately attractive.

    For one thing, OO was an immediately natural and intuitive approach - it didn't actually take much to understand it because it reflected "the real world".

    This doesn't. It's an entirely artificial construct that - ironically - is most easily understood when described in terms of the thing that I am now hearing it supercedes.

    i.e. an anon-method (closure) is an unidentified ref-counted instance of an unnamed class with a single method and a bunch of state that is implicitly captured.

    Replace those two "unnamed"s with "named" and "implicit" with "explicit" and I see that what we have is something that, well, bless my soul, we have already. And anyone using a TThread has been doing what anon-method suddenly "enable" us to do (in terms of one supposed example of something they enable that we couldn't do before).

    (TThread is far from being a good example of a thread implementation, but the principle is the thing of most relevance here, not the fitness of one particular implementation - I have my own - far superior, naturally, LOL thread encapsulation)

    So my stumbling block isn't "Hmm, this is complicated, I don't understand how it works" (not now at least that a couple of Q's have been cleared up) so much as "Hmmm, this looks unnecessarily clever - what is it actually useful for?"

    Those answers are the ones that are eluding me right now.

    And indeed a lot of other people, even the most enthusiastic ones. They seem seem enrapt in the possibilities yet are unable to say what those possibilities are.

    - The possibilities are endless.

    Right, so what are the possibilities...?

    - Oooooooohhhhhh, they're endless. Look at the wonderful endlessness of the possibilities. Endless they are. And possible too. Endlessly possible.

    Interesting. Tell you what come back to me when you've managed to pin down the end of one of those possibilities and can show it me.


  11. Started on a comment, but changed it to a new post. I can't really show since I don't have access to Tiburón, but I did outline a few possibilities in a new post.