Here's an odd corner of Delphi: expressions of a callable type (e.g. a function pointer or method pointer) are not fully-fledged expressions; or rather, the expression grammar for function application doesn't acknowledge function pointer types as proper first-class parts of the type system. For example:
type TFoo = procedure(x: Integer); TBar = function: TFoo; var bar: TBar; begin bar(42); // doesn't compile; too many args for TBar (bar)(42); // doesn't compile; bar's value doesn't flow through bar()(42); // still no good; bar()'s value doesn't flow through (bar())(42); // again, no good, for same reasons as previous two end.
The reasons are probably directly related to two things: the historical lack of general function pointers in Pascal and the rule that no-argument procedures can omit the parentheses.
The following are valid expressions that call the function pointer, but the resulting value can't be invoked in turn:
bar; // ok: return value dropped bar(); // ok: return value dropped
The above results in some serious drawbacks if one wants to program Delphi in a functional style. Currying can't work, for example: there's no way you could turn foo(1, 2, 3) into foo(1)(2)(3), since the latter syntax isn't valid.
I personally consider it a bug, but this behaviour, at the heart of expression parsing, is tricky to fix while guaranteeing not to break anything that already works. For example, there are other odd semantics around these Delphi function pointers:
type TFrob = function: Integer; function MyFrob: Integer; begin Result := 42; end; procedure Baz1(x: TFrob); begin end; procedure Baz2(x: Integer); begin end; procedure BazO(x: TFrob); overload; begin end; procedure BazO(x: Integer); overload; begin end; begin Baz1(MyFrob); // passing MyFrob Baz2(MyFrob); // calling MyFrob and passing result BazO(MyFrob); // guess which? end.
The above call to Baz1 is currently handled, believe it or not, by parsing as a function call and then later, in the middle of function application, throwing away the call bit and turning it back into taking the address of the function. You can see this by hiding the function call just a tiny bit, so the function application logic can't see it so easily:
Baz1((MyFrob)); // no longer passing MyFrob
The lesson to me in the above case is pretty clear. If you want to have function pointers in your language, you shouldn't make function application look like a function value - that way madness lies. This is why the '@' operator was invented:
Baz1((@MyFrob)); // works again; passing MyFrob
Unfortunately, its use in Delphi for these cases is optional.
The two issues discussed in the above post are distinct. However, I may need to fix the first one in order to get certain features into the product...
7 comments:
Hi Barry,
please fix it. This is just one of the things the Delphi compiler could be improved.
While it is easier to program omitting things like @ and the occasional ^-dereference-symbol, I dearly believe this is exactly what makes it harder to understand what the compiler does beneath the hood.
If this yet improves your possibilities to bring new features to the compiler, honest to god, go ahead and fix it.
Thanks so much!
Daniel
The problem here is that Delphi doesn't force you to specify whether or not you are referring to the function address or return value. For example, the following statements are distinct in C/C++:
x = myfunc;
x = myfunc();
The first instance assigns to x the value of the function address, and the second executes the function and assigns its result. The @-operator was invented to get by this problem, but relaxed to make for prettier syntax when assigning event handlers.
No real way past this AFAIK. If you want to use the constructs you showed, you will have to assign the intermediate value to a function variable.
cobus,
Yes, that's the gist of what I said. Technically, both of the problems I mentioned can be fixed in the compiler (so it's not quite as bad as "no real way past this"), but the first has a higher priority (and to be frank, a greater utility, IMHO). I will be fixing it for Tiburon, barring something unforeseen in compatibility issues.
Barry, mind if bring up another syntactical gripe? When assigning handlers to multicast events, I think it's truly heinous that this should be done via Include() and Exclude(). For some reason I find it offensive that an object needs "help" from outside itself (if you know what I mean) to get the job done.
RemObjects' Chrome uses "+=" or somesuch C-ism which isn't particularly attractive. Why not overload "add" and "remove" so they can be used outside of the class declaration? Then we could do something like
MyObj.OnChange add MyHandler ;
MyObj.OnChange remove MyHandler ;
which I find more aggreeable.
I'll be very pleased if you tell me to RTFM because such a thing already exists.
Cheers,
Mark.
Barry,
I like that you say it is fixable (and believe you, of course), but how will you get by the syntactic drawback of not being able to tell whether the code is looking for the function result or address? Or will you just impose forced use of () in function calls for such cases?
For example, suppose I have a function calling a function calling a function and I want the last function's address, we can do this (as you suggested before):
p := f()();
Except that for all other functions, the () syntax is only supported, not required, which means that this should be just as valid:
p := f();
So now we need some rule to say when () is required.
This could give the address of the second function:
p := @(f);
But I don't see how to get to the third from there in a fairly intuitive way using @.
Or perhaps my thinking is squarely within the box and I'm missing something?
Cobus,
It seems like quite the minefield doesn't it? It's not obvious to me how the situation can be definitively rectified while maintaining backward compatibility.
I read your statement
p := f()();
as calling the second function rather than obtaining the address of the third function :-)
Enforced use of @ and () helps, but breaks old code.
>MyFrob
procedural type has a more high priority than simple types (Integer, ...)
--
Vadim
Post a Comment