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