Jump to content
David Schwartz

Anon methods passed as event handlers?

Recommended Posts

The usual way of creating an event handler in Delphi is to double-click the right-hand field in the Events tab of the Object Inspector. This creates a method with the proper signature; when the form is loaded, the OnXyzEvent is assigned the method name:

 

FOnXyzEvent := aForm.XyzEvent;

 

In theory, it should be possible to do something like this for a typical event handler:

 

FOnXyzEvent := procedure(Sender: TObject)
               begin
                 doSomething;
               end;

However, I have a TpgScript object that has an AfterExecute event with this signature:

procedure TpgScriptEx.DoOnAfterExecute(Sender: TObject; SQL: string);
begin
//
end;

FScript.AfterExecute := DoOnAfterExecute; // this works fine

// but for this, the compiler complains:
// [dcc32 Error] myfile.pas(1258): E2010 Incompatible types: 'TAfterStatementExecuteEvent' and 'Procedure'
FScript.AfterExecute := procedure (Sender: TObject; SQL: string)
                        begin
                          //
                        end;

If I try casting the procedure, I get an Invalid Cast error.


This seems like it should be a compiler bug.

 

Is there a workaround for this?

 

Share this post


Link to post

An event is always declared as procedure(...) of object, which carry an invisible self parameter. This allows object methods, but excludes global procedure(...) and reference to procedure(...) declarations as well as anonymous methods, because they all lack that self parameter.

Share this post


Link to post
Posted (edited)

So can you add the Self param explicitly?

 

Or is there just no way to do it? 

 

I'd think that this might work, but it doesn't.

FScript.AfterExecute := procedure (Sender: TObject; SQL: string) of object
                        begin
                          //
                        end;

 

Edited by David Schwartz

Share this post


Link to post
Posted (edited)

FWIW, I found this on SO:

 

This is not possible. You have to make the event handler be a method type rather than an anonymous method. 

You'll need to wrap your anonymous method in a method. Either an instance method (of a record or a class), or a class method. For instance: 

 

 

Edited by David Schwartz

Share this post


Link to post
6 hours ago, David Schwartz said:

In theory, it should be possible to do something like this for a typical event handler:

No, it shouldn't, not in theory and not in practice. You have a different signature. An anonmeth is not a TNotifyEvent. They are two very different beasts.

Share this post


Link to post

Rudy, I get what you're saying, but that's not what I mean. I meant that it should be possible to have a syntactic construct that would allow you to define an inline anon method that works in all cases, as opposed to working for some but not others. If it means adding "of object" to the signature or whatever (plus maybe something else), so be it.

 

Right now it seems as arbitrary as, say, allowing you to use procedures but not functions, or vice versa.

Share this post


Link to post
39 minutes ago, David Schwartz said:

I meant that it should be possible to have a syntactic construct that would allow you to define an inline anon method that works in all cases, as opposed to working for some but not others. If it means adding "of object" to the signature or whatever (plus maybe something else), so be it.

That still won't work.  No amount of syntax sugar will account for the fact that an anonymous procedure simply does not carry a Self parameter that the event caller can pass in, or the fact that an anonymous procedure is implemented using a compiler-generated reference-counted interface, which is radically different than a simple method pointer.

 

Event handlers are currently implemented under the hood using the TMethod record.  When a event owner wants to call an event handler, the compiler has to generate a particular set of code to call the method that is referenced by the record, inserting the Self parameter that is referenced by the record.  In order to call an anonymous procedure, the compiler has to generate a completely different set of code to call the Invoke() method of the anonymous interface.  There is currently no way for the compiler to decide at the call site at compile-time which set of code it needs to generate based on what kind of handler is assigned at runtime.  Embarcadero would have to redesign the way TMethod and/or anonymous procedures are implemented in order to make anonymous procedures be compatible with TMethod, in a way that does not break existing user code.

 

I suppose Embarcadero could make it so when an anonymous procedure is assigned to a TMethod, the record is flagged in a way that lets the compiler know the Data field points to an anonymous interface instead of an object pointer, and the Code field points to the interface's Invoke() method, so the compiler can then branch with the appropriate code to execute Invoke() on the interface.  But that still leaves the issue that anonymous procedures are reference counted, and TMethod doesn't know anything about reference counting (outside of object ARC on mobile platforms - which is going away in upcoming Delphi versions).  So that is even more boiler plate code that the compiler has to implement everywhere TMethod is used to account for the reference count.  The Managed Records feature that was planned for 10.3 might have solved that, but that feature had to be deferred until 10.4 for technical reasons.

Share this post


Link to post
Posted (edited)

In 1961, when Pres. Kennedy announced that mankind would go to the moon by the end of the decade, there wasn't a single person alive who knew how to do it.

 

When Henry Ford told his engineers he wanted an 8-cylinder engine, there was nobody alive who believed it could be done.

 

And here you are redoubling your efforts to explain to me in ridiculous detail why the simple thing I want to see possible at the source code level is impossible to do.

 

The purpose of the compiler is to hide complexities like this from the user. Automatic reference counting wasn't viable ... until it was. Generics weren't considered viable ... until they were. There are so many things that people thought would be "nice to have" but weren't considered viable, and at the time there were perfectly valid technical arguments that were made about why they weren't either practical or even possible. Until they were.

 

A lot of contemporary programming languages support stuff that Delphi could support, but there needs to be the willingness on the part of the folks who maintain the Delphi compiler to actually embrace them. Delphi was revolutionary when it launched, but within a few years, it got stale, and the language has only improved slightly (IMO) in ways designed to appease the marketplace just to keep people from bailing out to other platforms entirely. C# has so many cool language features that I doubt we'll ever see in Delphi. I don't know why, because adding new language features to a product that's supported by pretty hefty annual fees makes a lot more sense than adding features to a product that's mostly funded by other product sales (as most of Microsoft's language products are). I mean, what incentive does Microsoft have to keep adding new features to a language that generates relatively small revenues? For one thing, you can argue customer lock-in. For another, newbies want to hitch their wagon to technology that's more leading-edge rather than stuff their parents or grandparents used.

 

I'm really not interested in the technical reasons why this suggestion isn't possible today. Just like I'm not interested in justifications why you can't use strings in case statements in Delphi while most other languages today allow it. There has to be a willingness on the part of the vendor to add these sorts of upgrades to the language instead of defending their absence, which is the normal case with Delphi.

 

You're just defending the status quo.

 

BTW, C++ was introduced about 10 years before Delphi. The first ANSI standards effort for C++ started about the same time Delphi was introduced. And now they just approved the 3rd iteration of the C++ standard, with work progressing on the 4th iteration. Meanwhile, Delphi has hardly had anything new added since the first C++ standard was released.

 

People pay Embarcadero a lot of money every year to maintain and upgrade Delphi. The backlog of bugs keeps increasing, and the language enhancements progress at a snail's pace.

 

What are we paying for exactly? Most of what we get are lots of technical arguments about why this or that language enhancement is not worth considering, while job reqs for Delphi dwindle  and vendors want to hire people with experience using the latest language features that aren't even on Delphi's radar.

 

Why in the world would anybody want to choose Delphi to develop a new product with a 10-15 year life span today? All the jobs I find advertised are for maintaining legacy apps. Where's the NEW product development happening with Delphi? Why would anybody want to hitch their wagon to an old, stale technology that's so far behind the language technology curve?

Edited by David Schwartz
  • Like 4

Share this post


Link to post
26 minutes ago, David Schwartz said:

<snip>

 

And here you are redoubling your efforts to explain to me in ridiculous detail why the simple thing I want to see possible at the source code level is impossible to do.

Pesky facts. You cannot do what you wish, with the product which exists today.

26 minutes ago, David Schwartz said:

<snip>

I'm really not interested in the technical reasons why this suggestion isn't possible today. Just like I'm not interested in justifications why you can't use strings in case statements in Delphi while most other languages today allow it. There has to be a willingness on the part of the vendor to add these sorts of upgrades to the language instead of defending their absence, which is the normal case with Delphi.

Facts don't yield to attitudes. Hard to say what the future holds, but so long as the pursuit of cross-platform seems to take a high priority, the enhancement of currently functional language elements is unlikely to be reconsidered, IMHO.

You asked how to do what you want, and the answer is that you can't. At least not now. We all have our pet wishes for Delphi features, and I think there could be astonishment at how little commonality of opinion there is.

Share this post


Link to post

@David Schwartz I made a wrapper for assigning anon methods to TField events. 

My experiences:

Pros:

  a.) quick and straightforward to assign them wherever you want.

Cons:

  a.) debugger can't evaluate sh*t inside the anon methods

  b.) you will never ever find again in the code jungle wtf. is happening and why

 

The moral of the story: Moon landing was fake.

  • Like 1

Share this post


Link to post
2 hours ago, Bill Meyer said:

Pesky facts. You cannot do what you wish, with the product which exists today.

Facts don't yield to attitudes.

I think you totally missed my point. As I mentioned, at one time, both automatic reference counting and generics were "impossible", due to "pesky facts".

 

Whatever happened to those "pesky facts", eh?

 

They may not yield to "attitudes" per se, but they do yield to people up the Management chain who have vision and determination to see the value of certain things over the long-haul.

 

I don't think there's anybody in Embt right now who has enough vision or determination to change the course of the Delphi language much over the foreseeable future.

 

Especially when there are so many willing and able-bodied defenders of the status quo around.

 

In another 10 years, most of us are going to be gone -- physically, mentally, or just because we retired. Then what?

Share this post


Link to post

David is making a good point here. If Emba were starting from scratch here, would they end up with all these different incompatible procedural types? I doubt it. 

  • Like 1

Share this post


Link to post
21 hours ago, David Schwartz said:

Rudy, I get what you're saying, but that's not what I mean. I meant that it should be possible to have a syntactic construct that would allow you to define an inline anon method that works in all cases, as opposed to working for some but not others. If it means adding "of object" to the signature or whatever (plus maybe something else), so be it.

 

Right now it seems as arbitrary as, say, allowing you to use procedures but not functions, or vice versa.

I don't think that makes sense.  If that type would be able to catch simple procedurals, methods and anonmeths, it would have to be prett complex, and why would you want to use a pretty complex type for even the simplest (plain procedural) function?

 

Also, an event needs two things: an object and a code pointer. It can't do without. A plain or static function does not have those, An anonymous method does not necessarily have these either.

Share this post


Link to post
Posted (edited)
5 minutes ago, Rudy Velthuis said:

I don't think that makes sense.  If that type would be able to catch simple procedurals, methods and anonmeths, it would have to be prett complex, and why would you want to use a pretty complex type for even the simplest (plain procedural) function?

 

Also, an event needs two things: an object and a code pointer. It can't do without. A plain or static function does not have those, An anonymous method does not necessarily have these either.

It seems to make sense in other languages, like C# to give one example. 

 

The very way you talk about it is symptomatic of the problem. Talking about implementation details. Clearly there has to be an implentarion, but programmers by and large want to be shielded from these details. 

Edited by David Heffernan
  • Thanks 1

Share this post


Link to post
14 hours ago, David Schwartz said:

As I mentioned, at one time, both automatic reference counting and generics were "impossible", due to "pesky facts". 

They were never "impossible". At the timeframe of Delphi 7 generics were already being considered. ARC was not on the horizon yet, but it was never considered "impossible". Fact is that some things might have been missing that made their implementation impossible.

 

But that does not mean that everything you can conceive is possible, or, even if it is possible, if it makes sense. The current distinction between plain procedural types, method pointers and "reference to" pointers makes sense. It doesn't make sense to try to unify them, as the underlying concepts are already different.

Share this post


Link to post
Posted (edited)
6 minutes ago, David Heffernan said:

It seems to make sense in other languages, like C# to give one example. 

Does it?

 

I haven't been using C# a lot, lately, but AFAIK, C# does not have global (non-method) functions. It may have static functions, but can you assign those to a delegate?

Can you assign its equivalent of an anonmeth to an event?

Edited by Rudy Velthuis

Share this post


Link to post
2 minutes ago, Rudy Velthuis said:

Does it?

 

I haven't been using C# a lot, lately, but AFAIK, C# does not have global (non-method) functions. It may have static functions, but can you assign those to a delegate?

Can you assign its equivalent of an anonmeth to an event?

Yes to all questions. 

Share this post


Link to post
8 minutes ago, David Heffernan said:

Yes to all questions. 

That's cool. ISTM that for Delphi, it would mean quite a few changes in the compiler.

 

And well, I still don't see why it should be done.

Share this post


Link to post
22 minutes ago, David Heffernan said:

The very way you talk about it is symptomatic of the problem. Talking about implementation details

What problem? I don't see any problem.

Yes, the implementation details are what makes these types incompatible, and perhaps this can be done differently, with a lot of changes in the implementations,

 

But what would the big benefit be? Is it worth it? IMO, no.

Share this post


Link to post
Posted (edited)
10 minutes ago, Rudy Velthuis said:

That's cool. ISTM that for Delphi, it would mean quite a few changes in the compiler.

 

And well, I still don't see why it should be done.

C# event types were the equivalent of method reference types in Delphi in the first place (in fact, even more heavyweight given the multicasting support), so a Delphi-style problem of how to shoehorn anonymous methods to events, when anonymous methods were added in C# 2.0, didn't arise.

 

IMO (and I think this is in agreement with you?), the only way to do this truly cleanly in Delphi would be to switch the event type from method pointers to method references (the latter already support at a syntax level assignments of regular methods and standalone routines). However, that would be a complete no-no for VCL backwards compatibility.

Edited by Chris Rolliston
typo

Share this post


Link to post
2 minutes ago, Chris Rolliston said:

C# event types were the equivalent of method reference types in Delphi in the first place (in fact, even more heavyweight given the multicasting support), so a Delphi-style problem of how to shoehorn anonymous methods to events, when anonymous methods were added in C# 2.0, didn't arise.

 

IMO (and I think this is in agreement with you?), the only way to do this truly cleanly in Delphi would be to switch the event type from method pointers to method references (the latter already support at a syntax level assignments of regular methods and standalone routines. However, that would be a complete no-no for VCL backwards compatibility.

Yes, indeed, in agreement with me.

 

Hmmm... they could have introduced it for FMX, I guess, but I'm still not sure if this would have been a good idea.

 

If people want the complexity of C#, then they should use C#. <g>

Share this post


Link to post
13 minutes ago, Rudy Velthuis said:

If people want the complexity of C#, then they should use C#

Er, the complexity is in Delphi with its multiple different procedural types. C# delegates are simple. 

  • Like 3

Share this post


Link to post
55 minutes ago, David Heffernan said:

Er, the complexity is in Delphi with its multiple different procedural types. C# delegates are simple. 

C# delegates are terribly complex, and that is why they work the way they do. Delphi has more or less lightweight implementations for procedural and method types. Good for us.

 

So I'll repeat it:  If people want the complexity of C#, then they should use C#.

Share this post


Link to post
16 hours ago, David Heffernan said:

David is making a good point here. If Emba were starting from scratch here, would they end up with all these different incompatible procedural types? I doubt it. 

Agreed. However, they are not starting over, and would be unlikely to do so, with the low popularity of Pascal today. Given the inertia in the existing code, and concerns over breaking changes, as well as the lack of deeply committed language heavyweights on development, I think that the trend to cross-platform is likely to continue to dominate.

I would like to be proved wrong on that.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×