Anonymous procedures (Was: Re: [fpc-devel] for-in-index loop)

Sven Barth pascaldragon at googlemail.com
Sat Jan 26 17:10:01 CET 2013


On 26.01.2013 16:34, Alexander Klenin wrote:
> On Sat, Jan 26, 2013 at 8:31 PM, Sven Barth <pascaldragon at googlemail.com> wrote:
>> On 25.01.2013 23:57, Alexander Klenin wrote:
>>> You have also proposed lambda-expressions:
>>>>
>>>> map.Iterate(lambda TFPGMapLongInt.TIteratorProc(aKey, aData) as
>>>> Writeln(aKey, ' => ', aData.ClassName));
>>>
>>>
>>> I think that they are not optimal in the proposed form.
>>> Note that the longest part of the expression is actually a restatement
>>> of the Iterate parameter type.
>>> I propose to allow omitting that:
>>>
>>> map.Iterate(lambda begin
>>>     Writeln(aKey, ' => ', aData.ClassName);
>>> end;)
>>>
>>> That is, basically, "lambda" means "use procedure definition from the
>>> parameter type".
>>
>> I have already thought about this as well, but this is simply not how Pascal
>> (and especially the compiler) works. In Pascal the result of an expression
>> is (at first) independantly of the result it is assigned to. For example:
>>
>> double1 := 1.0/2.0;
>>
>> here the expression on the right side will be evaluated with the smallest
>> possible precision that does not cause data loss (let's assume for now that
>> 1.0, 2.0 and 0.5 all can be represented by a Single) and only in the
>> assignment the result is converted to a Double.
>
>> Also there's the problem of overloaded procedures. Let's assume we have
>>
>> type
>>    TProc1 = reference of procedure(aArg: Integer);
>>    TProc2 = reference of procedure(aArg: String);
>>
>> procedure SomeProc(aArg: TProc1);
>> procedure SomeProc(aArg: TProc2);
>>
>> SomeProc(lambda as Writeln(aArg));
>>
>> Which SomeProc to choose? Even if the lamda's code could derive the argument
>> types directly (which must not be the case as you can see in the example, as
>> Writeln can take an Integer as well as a String) you'd still need to find
>> the correct procedure.
>
> Ok, then let's take just one step back:
>
> SomeProc(lambda TProc1 as Writeln(aArg));
>
> This way, but problems are solved -- procedure type is specified
> independently from the parameter type,
> and overload resolution will work.

While the compiler would be satisfied by this, this nevertheless leaves 
the problem that we (as in "the user of the language") don't really see 
where the aArg comes from...

I can already imagine many questions like the following appearing on the 
mailing list:

=== quote begin ===

type
   TMyProc = reference to function(aArg: Integer): String;

procedure SomeProc(aArg: Integer);
begin
   SomeProcTakingTMyProc(lambda TMyProc as IntToStr(aArg));
end;

Why doesn't the lambda expression return the value that's passed to 
SomeProc, but something completely different?!

=== quote end ===

>
>> Also the parameter names MUST be declared somewhere. This is Pascal. There
>> can't be a symbol name appearing suddenly in the middle of the code.
> They are declared in the definition of TProc1 type.
> This is similar to records -- fields are declared in type definition.
>
>> These three points are why I proposed my example as is: we need somehow the
>> signature of the lambda. Maybe "lambda(aArg: Integer) as Writeln(aArg)"
>> would be sufficient already (or "lambda(aArg: Integer): String as
>> IntToStr(aArg)" for a function).
> This just replaces "procedure" with "lambda", so nothing is gained.
> The possibility to omit duplicated parameter lists is, in my opinion,
> quite important
> for this whole feature to be useful.
>

Yes, I'm aware of that...

Maybe the idea that I presented in the mail in the for-in-index thread:

lambda TProc(aArg) as ...

TProc is used to bind the correct type and the names in the parentheses 
are mapped to the arguments of TProc.

>>> Independently, I propose a general shorthand for functions containing
>>> a single expression
>>> (and perhaps for procedures containing a single statement, but I am
>>> less certain here):
>>>
>>> function f(a, b: Integer): Integer;
>>> begin Result := a + b; end;
>>> =>
>>> function f(a, b: Integer): Integer as a + b;
>>
>> I don't know whether I'd want to extend this to single line
>> functions/procedures. This is where the language becomes less clear again.
>
> While I do not feel too strongly about this, consider these arguments:
> 1) Orthogonality is one of the important first principles of language design,
>    and of Pascal design in particular -- every feature should work in
> combination with every other.
>    So it is bad to allow shortcut syntax in procedures used as
> parameters, and disallow otherwise.
> 2) With the introduction of anonymous functions,
> function f(a, b: Integer): Integer;
> can be rewritten as
> f := function (a, b: Integer): Integer;
> which strengthens point (1) above.
> 3) Single-statement procedures/functions are moderately common -- including
> operator overloads, simple getters/setters, various utilities, nested
> helper procedures.
> Quite a few times, I resented the fact that extracting a single-line
> nested procedure,
> while clearing code at the point of calling, inconveniently increases
> total line count by five(!) lines,
> thus significantly offsetting the readability gains.
> With shortened syntax, line count will be incremented only by two,
> which is much better.
>

Ok, if you come with orthogonality...

Just one point to ensure we understand this the same: if we use the 
abbreviated syntax ("as STMT/EXPR") then no variable/type/constant 
declarations are possible.

What still bothers me a bit is the handling of result values... if we 
require the code after the AS to be an EXPR we'd need to implicitely 
assign the result value. What does now happen if the lambda's type does 
require a return value, but we don't provide one? What the otherway round?

E.g.

=== example begin ===

type
   TProc = reference to procedure;
   TIntFunc = reference to function;

x := lambda TProc as fMyMemo.Lines.Add('FooBar'); // Add returns Integer
y := lambda TIntFunc as Writeln('Foobar');

=== example end ===

This would be solved if we allow the code to be a statement, but would 
require the usage of "Result := ".

>>> with both shortcuts combined, it becomes possible to write something like
>>>
>>> sum := arr.Reduce(lambda as a + b;);
>>>
>>> Even further, Ruby (or Objective C)-like extension may be introduced to
>>> allow
>>> procedure arguments (or perhaps just the last one) to be separated by
>>> some special character instead of parenthesis, for example:
>>
>> No, no, no. No "special characters". If you want to differentiate something,
>> then use keywords.
>
> There is a limit to "keywords instead of punctuation" rule --
> once humanity invented the language where
> "a := b + c" was spelled "add a to b giving c".
> After trying to use that, the value of *some* punctuation was
> universally acknowledged :)

I do value punctuation. But this is Pascal, it's known to be based on 
keywords and to read more like English language unlike C....

> But in this case, perhaps you are right, how about (taking into
> account the discussion of lambda above):
>
> sum := a.Reduce with lambda TAdderFunc as a + b;
> or without type helpers:
> sum := Reduce with a, lambda TAdderFunc as a + b;

While I still don't know why you'd want to replace parenthesis I would 
definitely prefer this syntax over any ":" you can throw at me ^^

Regards,
Sven



More information about the fpc-devel mailing list