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

Alexander Klenin klenin at gmail.com
Sat Jan 26 16:34:36 CET 2013


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.

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

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

>> 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 :)
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;

--
Alexander S. Klenin



More information about the fpc-devel mailing list