[fpc-pascal] Re: Delphi's anonymous functions in Free Pascal

Arioch AriochThe at gmail.com
Wed Aug 29 10:36:48 CEST 2012


Florian Klämpfl wrote
> 
> 
>>> This is the prototypical way to run a function over each element in a
>>> collection, returning the results.
>> (map (lambda (x) (+ x 1)) '(1 2 3))
>> -> (2 3 4)
> 
> I still don't see why this cannot be done by procedure variables: one
> can easily pass a procedure reference to a compare function to any sort
> library call. 
> 

Sorry for resurrecting al thread, but p[erhaps better to keep things
together.
1) "anonymous methods" in Delphi are made in very Pascalish way, a lot of
wording, a lot of boilerplate.
That adds to readability, but makes their use much harder. If you have
relatively complex 4-5 lines function, then the overhead is slightly
noticeable. But such functions are a borderline to me, having them anonymous
hampers both ease of read and ease of debug. I know C++ fans how say that
begin/end are awfully long. Well, Delphi style of anonymous functions would
frighten them even more.

Here we are at dilemma. Pascal was devised in 1949 to look like Classic
monumental style building, or at least some "manufacturing plant" style
building made of bricks industrial way.
Functional style is like more like elegant houses made of paper and thin
plywood, something mayeb of Gothic or Japanese style, easily constructible
(composable) to any form you like. 

They really are hard to make together. You can read about Nemerle and Scala
though, about the attempts.

For example in C++ lambdas look total strangers by syntax, but that is
inevitable. When you have to type 20 chars of boilerplate for 3 char
function "x+y" - that is too much.
http://www.cprogramming.com/c++11/c++11-lambda-closures.html

However, other than that i like syntax for C++ lambda's. It is clearly
distinct for compiler, it separates and thus clearly defines captured
values, it is rather laconic.

2) however to treat "x+y" as a proper function definition one should have 
2.1) either typeless (at compile type, in runtime that may be dynamical
typing, duck typing, whatever) language like LISP or Erlang or Python
 even generic function
2.2) or strongly typed language with very sophisticated type infering like
Nemerle and Scala
"x + y" is a function, but the compiler should look at the context and make
guess what would be the types for x, y and result (latter would usually be
least common type for x and y);
So compiler looks at the context and turns - where appropriate - "x + y"
into
a generic "function<TX, TY, TRes> (const x:TX; const y:TY): TRes; begin
Result := x+y; end; "
Quite a lot to generate of three symbols.
Well, there are even more concise syntaxes, using jokers or omissions.
For example in collection.filter operations the Predicate is expected (it is
called collection.Where in Spring4Delphi and MS.Net). So then "_ > 2" or ">
2" would be correct function, implicated to
a generic "function<SomeContextDependantNumericType>  function( const value:
SCDNT); begin Result := value>2; end;
It would also be nice for compiler to make some merges. 

Surely all that cherries can not be gathered without heavy language and
compiler reworking.
Delphi tried to get at least some of those, but 1949 legacy feels there. The
question is about possible compromise. 

(**** Side note: type inferring is also very useful in Generics. ***)
Some very tiny part of it is even implemented in Delphi :-)

Consider { Chu Jetcheng, 2008-07-24 }
http://wiki.freepascal.org/Generics_proposals

var 
    Obj1: TGeneric of Integer, Real;
begin
    Obj1 := TGeneric.Create(32, 3.14);

This is quite un-orthodox proposal At the statement, a compiler know the
type of Obj1, but does not know the type of TGeneric<X,Y>. Since usually
rvalue is parsed with no look at lvalue, such inferring is hardly possible
at all. More traditional way would be like 

var 
    Obj1;
begin
    Obj1 := TGeneric<String, Integer>.Create(32, 3.14);

Where compiler "infers" type of Obj1 looking at the type of value used for
assignment.

It is even better if type would be something like
   TEvent<String, Record<Field1, Filed2, Array<Items>>>.
You may say - make an alias.
Type MyEventHere = TEvent<String, Record<Field1, Filed2,
Array<Items>>>.

Sometimes that works, for larger code blocks. 
But if you have to declare type for every procedure in your unit, that 
becomes a mess...

However that is well in the spirit of Wirth.
In 1949 AFAIR you could not have var x: array[0..10] of integer; You should
have pre-defined named alias for that array.
And "open arrays" functions parameters of Turbo Pascal 7 would be just a
schism and herecy for original Pascal specs.
But we now even have and enjoy dynamic arrays and generalyl do not consider
them large enough to have their own dedicated explicitly declared name.

(**** End of Side note. ***)

3) Why that matters ?

You can refactor program with more ease.

Imagine you just want to save some table to... XML file. Or write it to
string. Or maybe pass it to some processor, that would check its sanity.

What is the table ? Maybe it is dynamic "array of record ..... end;" ? Or
maybe it is TDataSet ? Or maybe it is TStringList.Rows ? Or any of that and
who knows what else ?
// that is not that spectacular. TDataSet internals are scarcely documented
spaghetti.  When soem in-memory dataset no more compiles in newer Delphi
you'd want to change it to dynamic array or TList, but that would ask to
re-write all the units using it! You would end up using TClientDataSet and
all its TField objects just to pass dynamic array to make a single iteration
over it and dispose. Okay, in the world of many-GHz many-core CPUs that does
not matter. But "while not q.eof do begin ... q.next; end;" loop is a bit
fragile and redundant in wording. //

Okay, you also want to filter it. Your user wants to see customers with most
sales.
So he enters a number and only wants to see those whose sales are above.

AFAIR TDataSet had some filtering event (or was it TBDEDataSet), array does
not and should be copied.
The logic is very different for same purpose again, depending on source data
type style.

But what if there then be one more filtering wished ?
Or what if consumer program needs to apply its internal filtering as well ?

In hypothetical 100% inferring functional style it could look like

procedure Button1OnClick(...);
var Consumer; Data;
begin
      Data := CurrentConsumersCollection;
// array, file object, TDataSet - whatever

      Consumer := ConsumerArray[RadioGroup1.ItemIndex];  
// Save to XML, save to network, show on screen - whatever
  
      if CheckBox1.checked then 
         Data := Data.Filter( _.total > StrToInt(editSalesThreshhold.Text );
      if CheckBox2.checked then 
         Data := Data.Filter( _.CityCode = StrToInt(editCityCode.Text );
      .....

     Data.Map(Consumer.StepMethod);
(* or Consumer.Process(Data) *)
end;

This function could potentially work with ANY kind of input data containers.

4) And here we come to "capturing" - why those so called "anonymous methods"
are better called "closures", why that C++ \starts lambdas not with "()" par
but with two pairs "[] ()".

What is the argument type of 
  "     Data := Data.Filter( _.CityCode = StrToInt(editCityCode.Text ); " ?

Obviously it only can be a predicate: function<our-data-record-type> (const
value:ODRT): boolean;
Otherwise collection should have some fore-knowledge how it would be used
later.

Alternatively, it can also have "pointer" second argument, sending all type
checking to sewer.
Just how non-Generic TList is practically done. Brr...

Ahem, but how would it get city code to compare ?
We cannot compile 1000 functions with all the reference values of CityCode.
So the function should "capture" value of "StrToInt(editCityCode.Text" from
calling place.
In OOP language terms that means we create some object where
"StrToInt(editCityCode.Text" is some private field. It is exactly how that
is implemented in Delphi - via TInterfacedObject with refcounting.

Compilers sees that the auto-generated function should have
StrToInt(editCityCode.Text ) as a parameter from external context. It also
knows it is not allowed to have due to argument type of Collection.Filter
method being single-argument predicate.
So it behind the hood creates that object with extra parameter like private
field.

Other possible approaches ? Well, that FPC wiki page mentions Extended
Pascal / GNU Pascal "scheme types", tapes tagged with constant values. That
maybe could be extended to be var-tagged.
Then there can be hypothetical "function(code:integer) CompareCityCode(const
r: record): boolean" type. If it can somehow be considered derived (okay,
assignment-compatible) from mere "function CompareCityCode(...):..." then
that would be the same idea mostly.

Most hardcore feature would probably be "partial application", i mentioned
Scala but even it has it with limitations.

Imagine we have 
function CompareCityCode(const value: our-data-record; const referenceCode:
integer): boolean;
begin Result := value.CityCode = referenceCode; end;

What would be result of "CompareCityCode(?, 10)" ? It could be a predicate,
it could be new boolean function, having single argument "value" and always
comparing with 10.
In VM-based languages like Lisp or Java/.Net that can indeed be implemented
as function generator.
In native languages that probably can only be implemented as a hidden object
with "10" stored to that private field again.

Why bother ? Well, just imagining how much code and how many functions and
names would have be written to implement that spectacular Button1Click with
its adapting to different datasources, different consumers, different lists
of filters on different conditions, etc.

And then one future day this code - now split to many many dependent
procedures and types - have to be reworked.

5) It is hard to implement all that laconic fluid features in "heavy
industrial" style language of 1949.
Probably would never happen to Pascal. But some parts, some ideas probably
can be implemented still.

For example in Delphi one can not do generic function "x+y" except is both
"x" and "y" are classes.
But if they are integers or floats - then compiler cannot make a difference,
less so infer the types.
Well, x+y is maybe nonsense, but what about Max(x,y), if it could be
implemented generically for any type in single place... Well, again i am
passing from lambdas to generics - but their practical use is entangled
heavily...

Surely 1949-styled Pascal can not make all that features 1st citizens.
The question is how much and in what style of compromise can be implemented.
Generics were also heresy for original Pascal. But they are implemented,
some in Delphi and to some seemingly less extent in FPC as well.

While i pity divergence of syntaxes between Delphi and FPC generics, i
strangle would not pity if FPc finally implement
lambdas/closures/anonymous-methods in non-Delphi style.
Since Delphi style is so Pascalish, that it heavily limits their practical
use.



--
View this message in context: http://free-pascal-general.1045716.n5.nabble.com/Delphi-s-anonymous-functions-in-Free-Pascal-tp4911527p5711032.html
Sent from the Free Pascal - General mailing list archive at Nabble.com.



More information about the fpc-pascal mailing list