Monday, August 04, 2008

I now get anonymous methods: but I really dislike them already...

The example below was taken from Andreano Lanusse's blog on anonymous methods.




After examining this example for a couple of minutes (yes, really it took me that long...) and looking at the other example in his blog, I think I've made my mind up. I detest anonymous methods already. IMHO (of course as always), I can only conclude that using anonymous methods make your code less readable (to me at least) and don't add any real value to the language. Still, I haven't found examples or explanations that help me to get into the anonymous camp. Even Craig Stuntz's doesn't help me get there.


I also realize that there is one principle that I live by that renders some of the 'advantages' of anonymous methods useless. Sure, using anonymous methods can help you to limit the amount of 'real' code in a method and thus make it a bit more easy to overview. Or, at least, that's one of the reasons give to use anonymous methods.

However, this 'advantage' is only of use if you write really long methods and functions. The more code you have, the sooner you will find yourself repeating things and the quicker an anonymous method can help you reduce your code. But, if I look at my own coding style, I try to make small methods and functions, archieving only a couple of things in it. That hardly ever takes more than 10-20 lines of code per method. If you are writing longer code, chances are that you better start that refactoring utility and split your code up. Most of times that will make your code better reusable and easier to maintain.


As for Craig's argument of being able to do things that a designer of a framework didn't think of: I think you are working against the principles in that framework or the framework simply is not well designed. But, that's maybe another personal style of programming. If I use somebody else's framework, I try to use it as intended. Maybe the 'hacking' you are talking about becomes a bit easier, but I for one are against hacking.


The last sentence reminds me: due to some technical problems, you might have missed my posting on why a developer wants to be a CIO (or not). If you are border tonight, you might want to have a peek.


Bye,


Bart

9 comments:

Sergey Antonov aka oxffff said...

Please read the article.
http://dn.codegear.com/article/33336

See (from arcticle)


NameOfOsloCustomersSortedByName = customers.Where(c => c.City == "Oslo").OrderBy(c => c.name).Select(c => c.Name)


Notice the similarity to the following SQL query:

select c.Name from customers c where c.City = 'Oslo' order by c.Name


Without A. methods you may still do this on D7, but current syntax(before support A.M) is to more complicated.
Emulate closure as heap object like on .NET or native Delphi. But you would do this manually.

P.S. Later may be CG adds some sort of smart compile for externals collections(Datasets) as (LINQ expression as string) for external collections providers to generate native(SQL ..) expression.
And for native collections(arrays, Lists ..) it would bind code at compile time.

From Russia.
oxffff.

Anonymous said...

It is just me (don't want to offend anybody !!!) ... but also CodeGear guys (maybe excepting Barry) ... are also puzzled by the new Delphi addition: closures....

Until a very good usage scenario (maybe inspired from C#, JavaScript, Ruby, Groovy...etc) of closures will not be provided (adapted to Delphi) ... we will just continue to be biased against the creepy syntax :) ...

I just assume that anon-methods (closures) were added to Delphi (currently just in Win32/ no .NET mention) to align to the current modern language features (.NET family + dynamic ones) and also to support future devel as native Delphi Linq-like syntax sugar [maybe using sets ?] and future C++0x lambdas... Just a guess...

Barry just promised us some real-word examples for Delphi closures ... so lets press Barry for releasing these gems :)

Bart Roozendaal said...

@Sergei: thanks for pointing that article out. That was very illustrative and entertaining. It even makes me wonder a bit that I might find a use for the anonymous methods.

However, the article still comes up with the same arguments like 'shorter notation' and 'take sway' laborious work. I don't want to get back to the very short notitations that you'd find in a C-program. I think there is a lot of value in 'clear code'.

But, that 33336-article is good. I think I'll read it again tomorrow :-)

Anonymous said...

@sergey:

NameOfOsloCustomersSortedByName = customers.Where(c => c.City == "Oslo").OrderBy(c => c.name).Select(c => c.Name)


Notice that each of Where, OrderBy and Select return a new collection, none of which, except the one returned by Select, is ultimately retained.

These sorts of side effects (useful and convenient, maybe, but side-effects none-the-less) are relatively harmless in a garbage collected environment. Those temporary but otherwise essentially unused collections will just hang around until they get collected.

Delphi.Win32 isn't garbage collected, unless you use interfaces which - unless things are to change quite significantly in Delphi.Win32 - ADDs it's own degree of work.


And that assumes that you consider deliberate side-effects to be "a good thing" in the first place.

I for one don't.

It undoudtedly makes for some sexy demo's but ultimately doesn't actually add very much benefit at all.


And of course, maybe those methods (Where, Order By etc) don't create anything at all.... in .NET at least you may simply be constructing an expression tree.

There is absolutely no way to know that from looking at the code of course.

Some would say no NEED to know that. I suggest you don't let those people loose in your critical application code.

They are the sort of people who "don't worry about the details" and in my experience the result of such an attitude is invariably not good.


I don't know if expression trees are in the mix for Delphi.Win32. I for one fervently hope not.

LINQ is an abomination.

The results are superficially impressive, but when you pull back the curtain what you find is not a wizard, but an old fashioned hack.

imho

Anonymous said...

List.Sort( Function(Item1,Item2 : Pointer)
Begin
REsult := MyType(Item1).Value-MyType(Item2).Value;
End);

Or

Procedure TMyThread.Execute;

Begin
Synchronize( Procedure Begin Form1.Caption := 'Starting'; End; );
Sleep(10000); // Or do real work;
Synchronize( Procedure Begin Form1.Caption := 'Still Working'; End; );
Sleep(5000); // Or do real work;
Synchronize( Procedure Begin Form1.Caption := 'Done'; End; );
End;

Jarle Stabell said...

sergey wrote:

NameOfOsloCustomersSortedByName = customers.Where(c => c.City == "Oslo").OrderBy(c => c.name).Select(c => c.Name)

Jolyon wrote:
Notice that each of Where, OrderBy and Select return a new collection, none of which, except the one returned by Select, is ultimately retained.
---


First, let me define two different ways of representing collections:

1. Extensional representation:

This is an array or TList or similar datastructure, where each member of the collection is explicitly present (the memory spent for the structure is linear with respect to the number of members)

2. Intensional representation:
Here only a "recipe" for how to enumerate the collection is stored.
The collection containing all integers between 1 and 1000 000 000 can be stored by storing two integers, 1 and 1 000 000 000, together with logic/algorithm for "filling in the blanks" (enumerating all integers between).

Back to the statement in question:

NameOfOsloCustomersSortedByName = customers.Where(c => c.City == "Oslo").OrderBy(c => c.name).Select(c => c.Name)


In a sensible implementation, there won't be three extensional collections created by this statement.
Probably the collection at the OrderBy will internally be extensional due to the nature of ordering, but the other two collections will likely be intensional.
The first one doesn't need to be extensional, the last one (select) may eventually become extensionalized, depending upon whatever happens next to this collection.


Think of SQL, if all the logical intermediate sets had to be extensionalized by the database engine, SQL would be totally useless the moment you start to join tables.

I'm in the camp of people you don't want in your code ;-), I think it is a good thing not to hardcode a lot of strategies into the code, like which collections needs to be extensionalized, and whether the extensionalization is to be distributed on all available cores and what will run single-threaded. (In a couple of years, some of your users will run your code on 48-core machines).

We desperately need a lot of abstraction mechanisms in order to utilize many-core machines.

I find that a lot of code is "collection manipulation" (for-loops often are intensional collections).


IMO, using old fashioned loops to manipulate collections and always hardcoding whether a collection is to be intensional or extensional will in the not so distant future look as dated as pre-SQL database queries.

To me, the lambda expression in this statement

NameOfOsloCustomersSortedByName = customers.Where(c => c.City == "Oslo").OrderBy(c => c.name).Select(c => c.Name)

is perfectly easy to read and it has great performance (assuming sensible implementations of Where, OrderBy and Select).

(I guess we all agree that hardcoding a constant like 'Oslo' in code is bad)

Craig said...

Jarle, you may already know this, but to be perfectly clear:

Probably the collection at the OrderBy will internally be extensional due to the nature of ordering, but the other two collections will likely be intensional.

In LINQ, if a collection supports IQueryable (most do, especially DBs, Entities, etc.), then nothing is ever returned until the resulting query expression is iterated. IOW, if "where" is "intentional," then OrderBy will be, too. There are no intermediate lists, at all, and even after the NameOfOsloCustomersSortedByName assignment has returned there is no new list in memory.

If you start to iterate over NameOfOsloCustomersSortedByName, then one list might be created which satisfies the entire query expression. If, however, the original collection is a DB back end, ORM layer, caching system, or the like and can do ordering internally, then you may not ever have the entire list, as the DB server will create it on the fly, and the query will discard records you've already iterated. If it sounds like magic, it isn't; you're just executing a DB query and iterating rows, even though the query looks more like C# rather than SQL.

Folks who want to understand the details of this should read Bruce Eckel's free book on the subject.

Bart Roozendaal said...

@Anonymous: your examples are just typical of why I dislike anonymous methods. They don't contribute anything to the power of the code, only mystify it. I'd rather spend an exta 2.3 nanoseconds on creating a proper function for a sort supporting function than typing it the way you're example shows. The same goes for the example with the captions. That is a *typical* example of why you want to use a function for displaying a status message. If at some point I want to have the mesage displayed in a statusbar instead of the caption of the form, I only have to modify the one function instead of going through my code and update all the anonymous methods.

Your reply only made me stronger in my believe against anonymousmethods :-)

Anonymous said...

I can see the usefulness on Anonymous methods but I agree that examples can be hard.

The synchronization thing given here is something I that really annoys me though. Here's how I would write it:

procedure TMyThread.UpdateStatus(AProgress: integer; AStatus: string);
begin
Synchronize(
procedure begin
Form1.Caption := AStatus;
Form1.ProgressBar.Position := AProgress;
end;
);
end;

Procedure TMyThread.Execute;
Begin
UpdateStatus(0, 'Starting');
Sleep(10000); // Or do real work;
UpdateStatus(1, 'Still Working');
Sleep(5000); // Or do real work;
UpdateStatus(2, 'Done');
End;

Today I have to write:

procedure TMyThread.UpdateStatusCall;
begin
Form1.Caption := FStatus;
Form1.ProgressBar.Position := FProgress;
end;

procedure TMyThread.UpdateStatus(AProgress: integer; AStatus: string);
begin
FStatus := AStatus;
FProgress := AProgress;
Synchronize(UpdateStatusCall);
end;

Procedure TMyThread.Execute;
Begin
UpdateStatus(0, 'Starting');
Sleep(10000); // Or do real work;
UpdateStatus(1, 'Still Working');
Sleep(5000); // Or do real work;
UpdateStatus(2, 'Done');
End;

Plus I have to add two fields to the class to store the data, which I really don't like.

(Admittedly, I do none of this today... I use a taskpool and post data to the main thread to update the UI, Synchronize is ugly and should be avoided!)