Another "MacGyver" moment

Posted by on in Blogs

Or, "More fun with Generics and Anonymous methods".

I'll just leave it up to you whether or not these utility functions are useful, but here they are:

type
Obj = class
class procedure Lock(O: TObject; Proc: TProc); static;
class procedure Using<T: class>(O: T; Proc: TProc<T>); static;
end;

class procedure Obj.Lock(O: TObject; Proc: TProc);
begin
TMonitor.Enter(O);
try
Proc();
finally
TMonitor.Exit(O);
end;
end;

class procedure Obj.Using<T>(O: T; Proc: TProc<T>);
begin
try
Proc(O);
finally
O.Free;
end;
end;

While very contrived, here's how you could use Obj.Using():

procedure TForm1.Button1Click(Sender: TObject);
begin
Obj.Using<TStringList>(TStringList.Create, procedure (List: TStringList)
begin
List.Add('One');
List.Add('Two');
List.Add('Three');
List.Add('Four');
ListBox1.Items := List;
end);
end;

And here's how you could use Obj.Lock():

procedure TMyObject.Process;
begin
Obj.Lock(Self, procedure
begin
//code executing within critical section
end);
end; 
Tags: CodeGear


About
Gold User, Rank: 83, Points: 11

Comments

  • Guest
    Jody Dawkins Thursday, 25 September 2008

    I like the Using example. That's a nice way to deal with needing a short lived object to do a minor bit of work. It's also a neat way to implement a feature from another language that's not natively supported.

    Pretty cool.

  • Guest
    Roddy Thursday, 25 September 2008

    Interesting. Almost, but not quite, entirely unlike smart pointers ;-)

    Something I haven't got my head round yet is this: Do the new Delphi Generics allow you to implement anything more closely resembling boost's scoped ptrs and other RAII techniques? Or is "Finally" still the single most common keyword apart from begin/end?

  • Guest
    Jim McKeeth Thursday, 25 September 2008

    Those are pretty creative. Generics and Anonymous really open the language up to a lot of creative usages like that.

  • Guest
    Daniel Lehmann Thursday, 25 September 2008

    This is pretty cool and I will probably start using that once my Delphi 2009 arrives (here in Germany the English versions aren't available yet and my trial has expired).

    Now if we had a Type Inference and a little more concise Anonymous Functions syntax it would look even sweeter:

    Obj.Using(TStringList.Create, List =>
    List.AddString('2')
    );

    Maybe next version...one can always hope ;)

  • Guest
    Daniel Lehmann Thursday, 25 September 2008

    Is it possible to use Exit(Something) inside of the anonymous function to exit an enclosing function? Is "Result" available in there?

  • Guest
    Andreas Hausladen Thursday, 25 September 2008

    > here in Germany the English versions aren’t available yet

    There is only one version with all supported languages. If I want I could install Delphi 2009 in Japanese (what I won't do because I wouldn't understand a single word :-) )

  • Guest
    Tjipke van der Plaats Thursday, 25 September 2008

    For the using case, I have an expirimental implementation that doesn't need anonymous methods:

    with Using.This(TStringList.Create) do
    begin
    Add('Hello');
    end;

    It uses Interfaces in the implemenation to keep reference

    A second form of which is was thinking (but did not implement yet)

    var: Str: TStringList;
    ...
    with Using.This(Str, TStringList.Create) do
    begin
    Str.Add('Hello');
    end;

  • Guest
    Tjipke van der Plaats Thursday, 25 September 2008

    Man the &lt and &gt where removed from both methods...

    both methods above are generic and so it was called like:

    Using.This&ltTStringList&gt(...

    Tjipke

  • Guest
    Allen Bauer Thursday, 25 September 2008

    Daniel,

    "Is it possible to use Exit(Something) inside of the anonymous function to exit an enclosing function? Is "Result" available in there?"

    No. The Anonymous function *is* a proper function and so it has it's own scope. "Exit" would only exit the anonymous function itself. The "Result" variable is not captured and is not available.

    Allen.

  • Guest
    Daniele Teti Thursday, 25 September 2008

    Very nice post...

  • Guest
    Rob Kennedy Thursday, 25 September 2008

    Tjipke, your code does not achieve the goal. "Using" statements provide two things for an object: limited scope and limited lifetime.

    Your first example does not provide any scope at all; there is no variable to use to refer to the new object. The second example uses the scope of a normal local variable.

    Neither of your examples gives a limited lifetime to the new object. You say you use interfaces, but they only have their reference counts decremented when the enclosing routine exits. They are not controlled by simple statement blocks. Leaving the "with" block will not cause any interfaces to be released.

  • Guest
    Peter Thursday, 25 September 2008

    I would prefer Static Class Instantiation. C++ can do, Delphi not. :-(

  • Guest
    Tjipke van der Plaats Thursday, 25 September 2008

    Rob Kennedy, thanks for your observation, but it is not correct. (But I guess that is my fault for not giving the implementing code, I don't have that currently here)

    Basically the limited scope is within the "with" block. The result of Using.This is an interface that contains the thing that is passed in (the stringlist in this case), so the scope is implicit.
    There is a bug in the first example: it should read UsingObject.Add('...'). (Guess that happens when you try to retrieve the code from memory). UsingObject is a property of that interface containing the stringlist.
    I also realized that always needing to use UsingObject to access the object you are using is no not be very 'readable' (and doesn't work for nested usings) and that's why it is still experimental. The second form is my current suggestion (to myself) for improving it.

    Then about the lifetime: it is maintained by the interface The result of Using.This is a reference to an interfaced object. When the with goes out of scope, the interfaced object is freed, and it frees the contained object.

    I hope it is clear. Sorry but the real code is currently on a VM that I can't reach now, I was hoping to release it to the public sometime...

    Tjipke

  • Guest
    Henrik R. Carlsen Thursday, 25 September 2008

    Please explain. I must admit I haven't seen the light yet.

    Why clutter up the Pascal syntax with this? The example shows the temporary use of a TStingList object. I can do that already:

    var
    List: TStringList;
    begin
    List := TStringList.Create;
    try
    List.Add('One');
    List.Add('Two');
    List.Add('Three');

    // This assignment should strictly spoken not do a
    // contents assignment but replace the pointer of the
    // List-object. The result is suspect code
    ListBox1.Items := List;

    finally
    List.Free;
    end;
    end;

    Yes. There's a few more lines but that's that. So where is the big invention?

  • Guest
    Stevie Friday, 26 September 2008

    Would be nice if future versions of Delphi supported a shortcut syntax for anonymous methods.

    So instead of:
    ---
    Obj.Lock(Self, procedure
    begin
    //code executing within critical section
    end);
    ---

    One could write:
    ---
    Obj.Lock(Self) do
    begin
    //code executing within critical section
    end;
    ---
    So if the last parameter is a TProc (so basically any methods that follow that signature), then the "do" syntax can be used for slightly cleaner looking code.

    And:
    ---
    Obj.Using(List: TStringList) do
    begin
    List.Add(’One’);
    List.Add(’Two’);
    List.Add(’Three’);
    List.Add(’Four’);
    ListBox1.Items := List;
    end;
    ---

    This syntax will be optional, but idea is it can be used anywhere when the last parameter is a TProc, TFunc, etc. (i.e. "reference to ..." type) then the compiler will allow the "do" syntax.

    Even your Synchronize method will look cleaner:
    From this:
    ---
    Synchronize(procedure
    begin
    with FBox do
    begin
    Canvas.Pen.Color := clBtnFace;
    PaintLine(Canvas, I, A);
    PaintLine(Canvas, J, B);
    Canvas.Pen.Color := clRed;
    PaintLine(Canvas, I, B);
    PaintLine(Canvas, J, A);
    end;
    end);

    ---

    to this:
    ---
    Synchronize do
    begin
    with FBox do
    begin
    Canvas.Pen.Color := clBtnFace;
    PaintLine(Canvas, I, A);
    PaintLine(Canvas, J, B);
    Canvas.Pen.Color := clRed;
    PaintLine(Canvas, I, B);
    PaintLine(Canvas, J, A);
    end;
    end;
    ---
    //2 empty parenthesis after Synchronize would be optional, as-in... Synchronize() do...

    My 0.02cents :)

  • Guest
    Allen Bauer Friday, 26 September 2008

    Stevie,

    Interesting suggestion. In the future, we will be looking for ways to simplify the syntax.

    Allen.

  • Guest
    Allen Bauer Friday, 26 September 2008

    Henrik,

    Just like I mentioned at the beginning of the post, "I’ll just leave it up to you whether or not these utility functions are useful,"

    Yes, for my very simple and contrived case, the "Using" technique is overkill. But that wasn't really the point.

    Allen.

  • Guest
    Allen Bauer Friday, 26 September 2008

    Tjipke,

    "When the with goes out of scope, the interfaced object is freed, and it frees the contained object."

    Unless I'm mistaken, all temporary interface references are released at the end of the enclosing method, not when the with goes out of scope. There is only *one* case that I'm aware of where automatic cleanup occurs prior to the end of a method and that is the for..in..do syntax. In that case interfaces and enumerator objects are freed at the end of the loop.

    Allen.

  • Guest
    Daniele Teti Friday, 26 September 2008

    Another Using for Object parameterless constructor:

    ...
    class procedure Using(Proc: TProc); static;
    ...

    class procedure Obj.Using(Proc: TProc);
    var
    o: T;
    begin
    o := T.Create;
    try
    Proc(O);
    finally
    O.Free;
    end;
    end;



    So you can call without creating object

    Obj.Using(procedure(o: TStringList)
    begin
    o.Add('Hello');
    end);



    My 2 Cents

  • Guest
    Bart Roozendaal Friday, 26 September 2008

    Now, I'm not on a crusade against anonymous methods, really I'm not. I'm keeping my eyes open, really, trust me :-).

    First, I'm not familiar with TMonitor (it apparently is part of the Delphi Parallel Library). The online help doesn't really give a clue. Maybe that needs a bit of updating? :-)

    <quote>
    Description
    This is Enter, a member of class TMonitor.
    </quote>

    But, given this example, I cannot see the added value compared to the 'traditional' implementation like:

    TMonitor.Enter(ListBox1);
    try
    ListBox1.Items.Add ('One');
    ListBox1.Items.Add ('Two');
    finally
    TMonitor.Exit (ListBox1);
    end;

    It might be that there's something about the DPL that I need to know before understanding the beauty of this example, but reading the code, I really don't see a point for an anonymous method. This is something that could be done using existing statements, without additional effort.

    To add to this though, I do see a bit of the elegance and at least this example is readable. I just don't see the added value for this particular case, sorry. But, if more than one statement is needed for creating the circumstances for a piece of arbitrairy code, I think this might help.

  • Please login first in order for you to submit comments

Check out more tips and tricks in this development video: