How to assign a function, returned by another function, to a function variable? The result rather than the generating function itself
Asked Answered
H

2

16

A function is returning an anonymous function. I would like to assign the result to a variable. However the compiler thinks that I am trying to assign the function and not the result of the function. How can I resolve this?

program Project9;

{$APPTYPE CONSOLE}

type
  TMyEvent = reference to function: string;

var
  v1: TMyEvent;

function GetHandler: TMyEvent;
begin
  Result := function: string
            begin
              Result := '';
            end;
end;

begin
  v1 := GetHandler;  // <- Incompatible types: 'TMyEvent' and 'Procedure'
end.

Note: I do have a workaround but I hope that this problem can be solved without introducing a wrapper:

program Project9;

{$APPTYPE CONSOLE}

type
  TMyEvent = reference to function: string;

  TWrapper = record
    FHandler: TMyEvent;
  end;

var
  v1: TMyEvent;

function GetHandler: TWrapper;
begin
  Result.FHandler := function: string
                     begin
                       Result := '';
                     end;
end;

begin
  v1 := GetHandler.FHandler;  // <- works

EDIT: this is not specific to anonymous or any special kind of functions: that is actual for any function returning the function, it was the same in Turbo Pascal before even the 1st Delphi arrived.

Hanhana answered 20/1, 2014 at 23:5 Comment(1)
Didn't think I should at first, but after seeing the answer I +1'd this question.Destine
C
19

If your anonymous methods/functions are paramless, you must assign with ();

v1 := GetHandler();

Without the parentheses Delphi will try to assign the function to the variable. The parens tell it to invoke the function and assign the function result to the variable.

Clog answered 20/1, 2014 at 23:34 Comment(2)
I wonder if the documentation highlights that. I remember that question in Delphi 2 time - somethign about 1996 yearGamine
@Arioch'The It does indeed. And has done for as long as my records can discern. Delphi 5 help is the same as today's help.Sooth
S
7

Delphi's function call syntax is a little different from most other languages. In most languages, in order to call a function you must use parens () after the function name, commonly referred to as the function call operator. If the function is simply named, and no parens supplied, that expression evaluates to the function without invoking a call.

So, with the C++ language as our example,

i = foo();

calls the function and stores the return value in i.

On the other hand,

fn = foo;

stores the address of the function in the function pointer variable fn.

Delphi varies from this, for a parameterless function, by allowing you to omit the parens, and yet still call the function. So in Delphi, the first line of code above could be written

i := foo;

and this would call the function.

Where it gets slightly tricky is if the function return type is a procedural type, a method, or an anonymous method, as you have found out.

In your scenario,

v1 := GetHandler;

is ambiguous in the eyes of the compiler. Because v1 is a variable whose type is an anonymous method, the compiler will never generate a call when parens are omitted. If it did generate a call then you would not be able to make the simple assignment of a function to a procedural type variable.

So the compiler switches to the behaviour that you find in languages like C++. You must supply parens if you wish for the function to be called. To make your code compile and work, write

v1 := GetHandler();

The documentation covers the issue in some detail. The key excerpt is this:

In assignment statements, the type of the variable on the left determines the interpretation of procedure or method pointers on the right.


Now, casting judgement, I find the idea that the context of an expression can determine its interpretation to be rather unsettling. This all stems from allowing function calls to be made when parens are omitted. I would rather have to use parens always and so avoid the ambiguities discussed above. In particular this would allow expression meaning to be independent of context.

To see what I mean, we return to my original example. Let us now be more specific about the types involved:

type
  TIntFunc = function: Integer;

function foo: Integer;
begin
  Result := 42;
end;

var
  i: Integer;
  fn: TIntFunc;

At this point we can write:

i := foo;  // i is an integer, so the function is called
fn := foo; // fn is a procedural type variable, so the function is not called

I personally find this state of affairs not at all satisfactory.

Sooth answered 21/1, 2014 at 7:39 Comment(7)
idea for abomination: TIntFunc = function: Integer; TGenIntFunc = function: TIntFunc; TGenGenIntFunc = ...; now i := gf(); {TGenIntFunc}; i := ggf()();... i guess the parser would be confused WHICH one to call. :-DGamine
@Arioch'The I don't think so. So long as i is of procedural or method type, then function calls will only be made with (). No implicit function call made.Sooth
But WHICH of the function calls is it, if the function returns the function ? i := gf() returns the function - would that function be implicitly called or not ?Gamine
@Arioch Depends on the type of the variable to the left of the assignment operatorSooth
I guess Delphi core developers have been cursing this "feature" of paren-less function application since its inception ;-) . But if you go down that road of making decisions depending on the context, why not be consequent and consider both the function type itself and its return type?! This would immediately disambiguate an assignment like v1 := GetHandler;, as the type of v1 requires to receive the return value, not the function.Benzedrine
@Benzedrine unless v1 was a variant typeSooth
Yes, it would not weed out every situation that would be ambiguous, just this one and maybe a few others. A variant type target, as you mention, is one exception. I also thought about whether you could create a function that has the same return type as the function it returns, but not sure if this is possible ... ;-)Benzedrine

© 2022 - 2024 — McMap. All rights reserved.