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;
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.
ReplyDeletePretty cool.
Interesting. Almost, but not quite, entirely unlike smart pointers ;-)
ReplyDeleteSomething 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?
Those are pretty creative. Generics and Anonymous really open the language up to a lot of creative usages like that.
ReplyDeleteThis 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).
ReplyDeleteNow 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 ;)
Is it possible to use Exit(Something) inside of the anonymous function to exit an enclosing function? Is "Result" available in there?
ReplyDelete> here in Germany the English versions aren’t available yet
ReplyDeleteThere 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 :-) )
For the using case, I have an expirimental implementation that doesn't need anonymous methods:
ReplyDeletewith 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;
Man the < and > where removed from both methods...
ReplyDeleteboth methods above are generic and so it was called like:
Using.This<TStringList>(...
Tjipke
Daniel,
ReplyDelete"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.
Very nice post...
ReplyDeleteTjipke, your code does not achieve the goal. "Using" statements provide two things for an object: limited scope and limited lifetime.
ReplyDeleteYour 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.
I would prefer Static Class Instantiation. C++ can do, Delphi not. :-(
ReplyDeleteRob 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)
ReplyDeleteBasically 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
Please explain. I must admit I haven't seen the light yet.
ReplyDeleteWhy 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?
Would be nice if future versions of Delphi supported a shortcut syntax for anonymous methods.
ReplyDeleteSo 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 :)
Stevie,
ReplyDeleteInteresting suggestion. In the future, we will be looking for ways to simplify the syntax.
Allen.
Henrik,
ReplyDeleteJust 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.
Tjipke,
ReplyDelete"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.
Another Using for Object parameterless constructor:
ReplyDelete...
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
Now, I'm not on a crusade against anonymous methods, really I'm not. I'm keeping my eyes open, really, trust me :-).
ReplyDeleteFirst, 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.
My problem with using generics/anon methods to add things to the language that could/should be added to the language is that it potentially results in a cacophony of implementations (that you need to understand the internal workings of each of, in order to appreciate the behaviour they introduce to the code you are reading).
ReplyDeletetry..finally may be more verbose but it is also instantly recognisable, transparent and most importantly /universally consistent/ in a way that these (and possible alternative implementations achieving the same thing) are not.
Oxygene's "using" syntax meets all three of these criteria (recognisability of course comes only with familiarity, but that is itself more likely since it is part of the language).
I sincerely hope that minor but very useful language improvements such as these will not now be fobbed off in future on the basis that "you can do it yourself using generics and anon methods".
So far all the anonymous methods samples I've seen look more like an evolved form of "with" code obfuscation, except with added hidden machinery so you can have more bugs than those mere obfuscation would introduce.
ReplyDeleteColor me strongly not convinced these do anything to improve code readability and maintainability.
#23, #24
ReplyDelete/signed..
1. For 'Using':
ReplyDeleteReport No: 63369 (RAID: 261057) Status: Open
Inline declaration of variables / "using" -like keyword
http://qc.codegear.com/wc/qcmain.aspx?d=63369
2. For 'lambdas' (aka "the better, smaller, cleaner, safer way to write the anon. routines" :-)) perhaps having,
a.) the begin/end block optional would help, IOW why for if {condition} then {statement}, for {cycle} do {statement} etc. the begin/end is optional and for function Foo the begin/end is compulsory?
b.) if the first keyword in lambda is 'function' then the compiler will "inject"/try to apply a 'Result:=' on the (compound) statement and will throw a syntax error is something is wrong.
Hence we'll have:
x:=2; y:=2;
a:=function: double; x+y;
- or - (of course you'll implement QC #63479 - Type inference, even if Barry says that the compiler must be adapted isn't? ;-) )
a:=function: typed; x+y;
FTR, personally I don't like
a:=function: x+y;
...because it's error prone. This POV isn't mine, but I subscribe to it. We had a discussion in .non-tech some months ago where we settled all these. See the QC report for the details.