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...