Lazarus

Free Pascal => Beginners => Topic started by: Pluto on March 28, 2021, 07:36:15 pm

Title: How to assign an anonymous function
Post by: Pluto on March 28, 2021, 07:36:15 pm
I'm waiting for that ah-ha moment when I can start to figure out really simple things.... Every piece of documentation I can find seems to say this should work, yet I get a compilation error.  I feel dumb for asking, but why doesn't this work?  Is there some compiler directive I have to give to let this thing do anonymous functions?


var
  myFilterFunction:function(s:String):boolean;

begin
  writeln('test harness');
  myFilterFunction:=function(s:String):boolean  { this is line 17 }
    begin
      result:=s.StartsWith('t');
    end;
  readln();
end.


Compile Project, Target: MyTestProject.exe: Exit code 1, Errors: 2
MyTestsProject.lpr(17,21) Error: Illegal expression
MyTestsProject.lpr(17,29) Fatal: Syntax error, ";" expected but "(" found
Title: Re: How to assign an anonymous function
Post by: lucamar on March 28, 2021, 07:49:05 pm
IIRC, that construction is how they will work, but it isn't yet done. Unless it's in trunk, which I don't know ... :-[
Title: Re: How to assign an anonymous function
Post by: Pluto on March 28, 2021, 08:05:21 pm
Thanks for the quick responses...For context I'm trying to use some of those great functions in TStrings for manipulating data (like filter())... Hoping to find a more general-purpose library using generics... ;)
Title: Re: How to assign an anonymous function
Post by: Pluto on March 28, 2021, 08:50:53 pm
Well, I'm coming from languages where I haven't written a for-loop in years (Edit: slight exaggeration).  Things like DLINQ (C#) and Streams API (Java) relegate the looping to the base API.  The advantage of that is three-fold... (1) It cuts the amount of code you need to write in half, (2) it produces a more declarative syntax that is easier to read and understand, and (3) it allows the API to handle the parallelization, so you get it for-free, rather than having to write it yourself with async/await (or worse, spawning threads)...

But its a paradigm shift that requires an easy way to parameterize functions...  The library itself would be fairly easy to write, because we could just imitate the Java library...

I didn't quite understand what you said about Generics, Jamie.  Are you saying Generics actually replicates user's code at compile time?
Title: Re: How to assign an anonymous function
Post by: avk on March 28, 2021, 09:13:28 pm
This (https://github.com/avk959/LGenerics) library offers the use of filters, maps, foldings and other functions. Although, in the absence of anonymous functions, this is not so convenient.
Honestly, I'd like to believe that the problem of code bloat when using generics is outweighed by simpler code reuse.
Title: Re: How to assign an anonymous function
Post by: Martin_fr on March 28, 2021, 10:06:27 pm
Well you can write functions like: map, filter, sort, ...

In fact some do already exist: TStringList.CustomSort()

But, you can not write the "helper function" (e.g. compare for sort) on the fly.
You have to declare it upfront.

And also when you write your sort, you have to decide what kind of "function" it accepts.
This is not about the function arguments, which obviously must match.
This is about:
- plain function: "function Foo(); begin end"
- Method (of a class): "function TMyClass.Foo; begin end"
- Nested functions: Afaik needs a special modeswitch.
They all have different signatures, and are not assignment compatible.

You also do not have closures (not the same as anonymous functions / even though a lot of languages only offer the two as one bundled package).


But there is one good thing as well.

generic functions.

You can write a sort function as a generic, and then specialize it  (afaik possible on the fly) for any type (int, real, string, even class).
However, only if the operators are available or overloaded. => If you use > and < then those must be available for whatever type you want to compare.
Title: Re: How to assign an anonymous function
Post by: Pluto on March 28, 2021, 10:35:54 pm
This is all good stuff, but the magic happens in the parameterization of the function...  Would love to implement anonymous functions (and even lamda), but it might be above my head.  Edit: maybe I'll see if I can pull the free pascal source and try to understand how that would be done...
Title: Re: How to assign an anonymous function
Post by: Pluto on March 28, 2021, 10:47:09 pm
Meh.  Maybe I'm overthinking it... I tend to do that.  For a good introductory article on the streaming api (other than his for-loop examples being incomplete), see https://blog.jdriven.com/2019/10/loop
Title: Re: How to assign an anonymous function
Post by: engkin on March 28, 2021, 11:47:08 pm
Did I get his last example right?

The author seems to believe this code is unreadable and will cause problems a few months down the road:
Code: Java  [Select][+][-]
  1.   for (Person p : persons) {
  2.         for (Person sibling : p.getSiblings()) {
  3.             if (
  4.                sibling.getGender().equals("M") &&
  5.                sibling.getAge() > 18 &&
  6.                sibling.getAge() <= 65 &&
  7.                sibling.getName() != null &&
  8.                sibling.getName().startsWith("B")) {
  9.                 result.add(sibling.getName());
  10.             }
  11.         }
  12.     }

While, according to the author, this one is the perfect solution:
Code: Java  [Select][+][-]
  1.   List<String> result = persons.stream()
  2.           .flatMap(p -> p.getSiblings().stream())
  3.           .filter(p -> p.getGender().equals("M"))
  4.           .filter(p -> p.getAge() > 18)
  5.           .filter(p -> p.getAge() <= 65)
  6.           .filter(p -> p.getName() != null)
  7.           .filter(p -> p.getName().startsWith("B"))
  8.           .map(p -> p.getName())
  9.           .collect(Collectors.toList());
Title: Re: How to assign an anonymous function
Post by: Pluto on March 29, 2021, 02:57:33 am
The author seems to believe this code is unreadable and will cause problems a few months down the road:

Did I mention that sometimes I over-think things?
Title: Re: How to assign an anonymous function
Post by: engkin on March 29, 2021, 03:24:31 am
The author seems to believe this code is unreadable and will cause problems a few months down the road:

Did I mention that sometimes I over-think things?

Yes, but I mean no, I was not talking about you. It is the blog post you linked to. I found it odd that the writer of that blog post did not notice how by only changing the formatting of the code, how this alone,  makes both approaches almost identical.
Title: Re: How to assign an anonymous function
Post by: egsuh on March 29, 2021, 04:41:17 am
I've seen many questions on anynymous functions. I've done some programming in javascript, and think that anonymous functions are most similar to subroutines within subroutines. Pascal still needs name of the subroutine. --- nonymous. 

Code: Pascal  [Select][+][-]
  1. procedure ......
  2. var
  3.    anyvar: variant;
  4.  
  5.    function  myFilterFunction (s:String):boolean;
  6.     begin
  7.       result:=s.StartsWith('t');
  8.     end;
  9.  
  10. begin
  11.   writeln('test harness');
  12.   myFilterFunction;
  13.   readln();
  14. end.
  15.  


Regarding "for" statements, I would write as follows (not every construct will support for... in):

Code: Pascal  [Select][+][-]
  1. for p in persons do
  2.    with p do begin
  3.         if Gender <> 'M'  then continue;
  4.         if Age <= 18  then continue;
  5.         if Age > 65   then continue;
  6.         if Name = ''  then continue;
  7.  
  8.         List.Add(p);
  9. end;

I'd rather have something like

            if MoutineHeight in [15000, 20000..40000, >90000] then...   

etc.
Title: Re: How to assign an anonymous function
Post by: Pluto on March 29, 2021, 05:13:30 am
...And when someone comes to a language from another language, they carry a lot of preconceived notions, whether they realize it or not...  And so it takes them time to acclimate and fully understand the implications of how things are done... egsuh makes a very good implicit point that Pascal's superior math and underlying set theory provides some alternatives that I wasn't even considering...
Title: Re: How to assign an anonymous function
Post by: avk on March 29, 2021, 05:41:45 am
Did I get his last example right?

The author seems to believe this code is unreadable and will cause problems a few months down the road...

Well, yes, as usual hype, but sometimes it's funny.
For example, how would you solve such a problem?

Let Memo1 contain a list of words(just ASCII for simplicity) separated by tabs, spaces, commas, semicolons, or just line breaks.
When Button1 is pressed, it is required to split this list into words, convert these words to lower case and display in Memo1 non-repeating words consisting of three or more characters and which are palindromes(in lexicographic order, each word on a separate line), or display a message that there are no palindromes.
Title: Re: How to assign an anonymous function
Post by: engkin on March 29, 2021, 06:04:04 am
Code: Pascal  [Select][+][-]
  1.         if Gender <> 'M'  then continue;
  2.         if Age <= 18  then continue;
  3.         if Age > 65   then continue;
  4.         if Name = ''  then continue;

Why would you do the conditions this way? I would have expected something along:
Code: Pascal  [Select][+][-]
  1.      if (Gender = 'M') and
  2.         (Age > 18)  and
  3.         (Age <= 65)   and
  4.         (Name <> '')  then
  5.  

What did I miss?
Title: Re: How to assign an anonymous function
Post by: egsuh on March 29, 2021, 06:55:52 am
@engkin

No difference. Just for cases when there are other codes between the condition checks.
Title: Re: How to assign an anonymous function
Post by: avk on March 29, 2021, 12:59:44 pm
Well, yes, as usual hype, but sometimes it's funny.
For example, how would you solve such a problem?
...

Okay, then I'll try to answer my question myself.
A fairly typical Pascal code for solving this problem might look like this:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   Words: TStringArray;
  4.   Palindromes: TStringList;
  5.   I: Integer;
  6.   s: string;
  7. begin
  8.   Words := Memo1.Lines.Text.Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty);
  9.   for I := 0 to High(Words) do
  10.     Words[I] := LowerCase(Words[I]);
  11.   Palindromes := TStringList.Create;
  12.   try
  13.     for I := 0 to High(Words) do
  14.       if (Words[I].Length > 2) and (Words[I] = ReverseString(Words[I])) then
  15.         Palindromes.Add(Words[I]);
  16.     if Palindromes.Count <> 0 then
  17.       begin
  18.         Palindromes.Sort;
  19.         I := 0;
  20.         while I < Palindromes.Count do
  21.           begin
  22.             s := Palindromes[I];
  23.             Memo1.Append(s);
  24.             repeat
  25.               Inc(I);
  26.             until (I >= Palindromes.Count) or (Palindromes[I] <> s);
  27.           end;
  28.       end
  29.     else
  30.       Memo1.Append('no palindromes');
  31.   finally
  32.     Palindromes.Free;
  33.   end;
  34. end;  
  35.  

But it could also look like this:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2.   function ToLower(const s: string): string; begin ToLower := LowerCase(s) end;
  3.   function Fits(const s: string): Boolean; begin Fits := (s.Length > 2) and (s = ReverseString(s)) end;
  4.   function Less(const L, R: string): Boolean; begin Less := L < R end;
  5.   function Join(const L, R: string): string; begin Join := R + LineEnding + L end;
  6. begin
  7.   Memo1.Append(Memo1.Lines.Text
  8.     .Words([#9, #10, #13, ' ', ',', ';'])
  9.     .Map(@ToLower)
  10.     .Select(@Fits)
  11.     .Distinct(@Less)
  12.     .Fold(@Join)
  13.     .OrElse('no palindromes')
  14.   );
  15. end;
  16.  

And, of course, everyone can have their own opinion, which of the solutions is more readable and simpler.
Title: Re: How to assign an anonymous function
Post by: engkin on March 29, 2021, 07:05:18 pm
Sorry, did not mean not to answer your question. I missed your post. I think I have a solution probably based on a generic sorted set. Might customize its add function. But yes, I agree, the second solution looks beautiful. I did not say otherwise.

This line is remarkable:
Code: Pascal  [Select][+][-]
  1.     .OrElse('no palindromes')
Title: Re: How to assign an anonymous function
Post by: Martin_fr on March 29, 2021, 08:36:35 pm
With the appropriate helpers, I think the original code boils down to (not tested):
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   &Word: String;
  4. begin
  5.   LastWord := '';
  6.   for &Word in
  7.      lowercase(Memo1.Lines.Text)
  8.      .Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty)
  9.      .Sort // could be a normal function, "for word in sort(memo....split) do"
  10.   do
  11.   begin
  12.     If &Word = LastWord then continue;
  13.     LastWord := &Word;
  14.     if (&Word.Length <=2) or (&Word <> ReverseString(&Word))  then continue;
  15.  
  16.     Memo1.Append(&Word);
  17.   end;
  18.  

IMHO: Ugly, with all that is going into the for loop header....
Title: Re: How to assign an anonymous function
Post by: engkin on March 29, 2021, 09:11:55 pm
The class:
Code: Pascal  [Select][+][-]
  1. uses
  2.   LazUTF8, StrUtils, Generics.Collections;
  3.  
  4. type
  5.   TWordSet = specialize TSortedHashSet<String>;
  6.  
  7.   TPalindromeSet = class(TWordSet)
  8.     function Add(constref AValue: String): Boolean; override;
  9.   end;
  10.  
  11. function TPalindromeSet.Add(constref AValue: String): Boolean;
  12. begin
  13.   if (UTF8Length(AValue)>2) and (AValue = UTF8ReverseString(AValue)) then
  14.     Result:=inherited Add(AValue)
  15.   else
  16.     Result:=False;
  17. end;
  18.  

Usage:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   Palindromes: TPalindromeSet;
  4.   Words: TStringArray;
  5.   AWord: string;
  6. begin
  7.   Words := Memo1.Lines.Text
  8.            .ToLower
  9.            .Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty);
  10.  
  11.   Palindromes := TPalindromeSet.Create();
  12.   try
  13.     for AWord in Words do
  14.       Palindromes.Add(AWord);
  15.  
  16.     for AWord in Palindromes do
  17.       Memo1.Append(AWord);
  18.  
  19.     if Palindromes.Count=0 then
  20.       Memo1.Append('no palindromes');
  21.   finally
  22.     SetLength(Words, 0);
  23.     Palindromes.Free;
  24.   end;
  25. end;

Probably adding words could be part of the class, AddWords. But the idea is simple, the class has to deal with its duty, only adding words that are palindromes. It makes it possible to focus on the real problem. This includes adding words from other languages.

Test string with English, Hebrew, Arabic and Chinese words.
Quote
ולכשתשכלו;BB;AA;CCC;MMM;AAA;MeeM:ABA;AABB;ABBA;BAAB;MAN;NAM;MAM;NAN;FAT;BAAB;FAAF;باب;文言文
Title: Re: How to assign an anonymous function
Post by: Pluto on March 30, 2021, 04:44:03 am
Thank you.  Well done.
Title: Re: How to assign an anonymous function
Post by: Blade on March 30, 2021, 06:41:20 am
I'm waiting for that ah-ha moment when I can start to figure out really simple things.... Every piece of documentation I can find seems to say this should work, yet I get a compilation error.  I feel dumb for asking, but why doesn't this work?  Is there some compiler directive I have to give to let this thing do anonymous functions?

You might want to check out this thread.  Very interesting debate, workarounds, and examples.

https://forum.lazarus.freepascal.org/index.php?topic=45818.0 (https://forum.lazarus.freepascal.org/index.php?topic=45818.0)
(Functional programming in Pascal)

Quote
SymbolicFrank
What is the problem that anonymous functions solve?

Code: Pascal  [Select][+][-]
  1. function IsNegative(SomeParam: Integer): Boolean;
  2. var
  3.   SomeVar: Integer;
  4.  
  5.   function Calculate: Boolean;
  6.   begin
  7.     Result := False;
  8.     if SomeVar < 0 then Result := True;
  9.   end;
  10.  
  11. begin
  12.   Result := Calculate;
  13. end;
  14.  
Title: Re: How to assign an anonymous function
Post by: Leledumbo on March 30, 2021, 09:15:44 am
Code: Pascal  [Select][+][-]
  1. function IsNegative(SomeParam: Integer): Boolean;
  2. var
  3.   SomeVar: Integer;
  4.  
  5.   function Calculate: Boolean;
  6.   begin
  7.     Result := False;
  8.     if SomeVar < 0 then Result := True;
  9.   end;
  10.  
  11. begin
  12.   Result := Calculate;
  13. end;
  14.  
One deficiency I admit I hope it wasn't there in the first place. It's a rare condition, but once you're trapped in, you might not realized where the problem is.
Title: Re: How to assign an anonymous function
Post by: PascalDragon on March 30, 2021, 09:34:04 am
With the appropriate helpers, I think the original code boils down to (not tested):
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   &Word: String;
  4. begin
  5.   LastWord := '';
  6.   for &Word in
  7.      lowercase(Memo1.Lines.Text)
  8.      .Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty)
  9.      .Sort // could be a normal function, "for word in sort(memo....split) do"
  10.   do
  11.   begin
  12.     If &Word = LastWord then continue;
  13.     LastWord := &Word;
  14.     if (&Word.Length <=2) or (&Word <> ReverseString(&Word))  then continue;
  15.  
  16.     Memo1.Append(&Word);
  17.   end;
  18.  

IMHO: Ugly, with all that is going into the for loop header....

Sidenote: you don't need to escape Word as it's a type, not a keyword.
Title: Re: How to assign an anonymous function
Post by: lainz on March 30, 2021, 04:51:38 pm
Lately I had to rewrite 'functional' programming because it was slower than a traditional for loop.

The main problem is if you have a big array (100k objects) and what is being done in each .something.something you concat is to create a new array!!! Of course array of pointers but nevermind, it was slow and I get the same result with more lines of code but faster.

So think twice when one liners are best or not... Measure their speed.
Title: Re: How to assign an anonymous function
Post by: Leledumbo on March 30, 2021, 11:57:58 pm
Lately I had to rewrite 'functional' programming because it was slower than a traditional for loop.

The main problem is if you have a big array (100k objects) and what is being done in each .something.something you concat is to create a new array!!! Of course array of pointers but nevermind, it was slow and I get the same result with more lines of code but faster.

So think twice when one liners are best or not... Measure their speed.
That's actually one core principle of (pure) functional programming: immutable objects. That's why (almost?) every functional programming languages are garbage collected.
Title: Re: How to assign an anonymous function
Post by: avk on March 31, 2021, 05:41:13 am
Lately I had to rewrite 'functional' programming because it was slower than a traditional for loop.

The main problem is if you have a big array (100k objects) and what is being done in each .something.something you concat is to create a new array!!! Of course array of pointers but nevermind, it was slow and I get the same result with more lines of code but faster.

So think twice when one liners are best or not... Measure their speed.

At least in LGenerics, the TGEnumearble class is just a wrapper over an enumerator and usually doesn't create any additional arrays. The exceptions are the Sorted(), Distinct() and sometimes Reverse() methods, but I just don't know how to implement them differently.
TinyPortal © 2005-2018