How to assign an anonymous function lazarus / 61-Beginners ---------------------------------------------------------------------- Pluto · 2021-03-28 17:36 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 ---------------------------------------------------------------------- lucamar · 2021-03-28 17:49 IIRC, that construction is how they will work, but it isn't yet done. Unless it's in trunk, which I don't know ... ---------------------------------------------------------------------- Pluto · 2021-03-28 18:05 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... ---------------------------------------------------------------------- Pluto · 2021-03-28 18:50 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? ---------------------------------------------------------------------- avk · 2021-03-28 19:13 This 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. ---------------------------------------------------------------------- Martin_fr · 2021-03-28 20:06 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. ---------------------------------------------------------------------- Pluto · 2021-03-28 20:35 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... ---------------------------------------------------------------------- Pluto · 2021-03-28 20:47 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 ---------------------------------------------------------------------- engkin · 2021-03-28 21:47 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   for (Person p : persons) {        for (Person sibling : p.getSiblings()) {            if (               sibling.getGender().equals("M") &&             sibling.getAge() > 18 &&               sibling.getAge() <= 65 &&               sibling.getName() != null &&               sibling.getName().startsWith("B")) {                result.add(sibling.getName());            }        }    } While, according to the author, this one is the perfect solution: Code: Java  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   List result = persons.stream()          .flatMap(p -> p.getSiblings().stream())          .filter(p -> p.getGender().equals("M"))          .filter(p -> p.getAge() > 18)        .filter(p -> p.getAge() <= 65)          .filter(p -> p.getName() != null)          .filter(p -> p.getName().startsWith("B"))          .map(p -> p.getName())          .collect(Collectors.toList()); ---------------------------------------------------------------------- Pluto · 2021-03-29 00:57 Quote from: engkin on March 28, 2021, 11:47:08 pmThe 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? ---------------------------------------------------------------------- engkin · 2021-03-29 01:24 Quote from: Pluto on March 29, 2021, 02:57:33 amQuote from: engkin on March 28, 2021, 11:47:08 pmThe 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. ---------------------------------------------------------------------- egsuh · 2021-03-29 02:41 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure ......var   anyvar: variant;     function  myFilterFunction (s:String):boolean;    begin      result:=s.StartsWith('t');    end; begin  writeln('test harness');  myFilterFunction;   readln();end. Regarding "for" statements, I would write as follows (not every construct will support for... in): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i for p in persons do    with p do begin        if Gender <> 'M'  then continue;       if Age <= 18  then continue;         if Age > 65   then continue;         if Name = ''  then continue;          List.Add(p); end; I'd rather have something like             if MoutineHeight in [15000, 20000..40000, >90000] then...    etc. ---------------------------------------------------------------------- Pluto · 2021-03-29 03:13 ...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... ---------------------------------------------------------------------- avk · 2021-03-29 03:41 Quote from: engkin on March 28, 2021, 11:47:08 pmDid 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. ---------------------------------------------------------------------- engkin · 2021-03-29 04:04 Quote from: egsuh on March 29, 2021, 04:41:17 amCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i         if Gender <> 'M'  then continue;         if Age <= 18  then continue;       if Age > 65   then continue;         if Name = ''  then continue; Why would you do the conditions this way? I would have expected something along: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i      if (Gender = 'M') and         (Age > 18)  and         (Age <= 65)   and       (Name <> '')  then What did I miss? ---------------------------------------------------------------------- egsuh · 2021-03-29 04:55 @engkin No difference. Just for cases when there are other codes between the condition checks. ---------------------------------------------------------------------- avk · 2021-03-29 10:59 Quote from: avk on March 29, 2021, 05:41:45 amWell, 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  Words: TStringArray;  Palindromes: TStringList;  I: Integer;  s: string;begin  Words := Memo1.Lines.Text.Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty);  for I := 0 to High(Words) do    Words[I] := LowerCase(Words[I]);  Palindromes := TStringList.Create;  try    for I := 0 to High(Words) do      if (Words[I].Length > 2) and (Words[I] = ReverseString(Words[I])) then        Palindromes.Add(Words[I]);    if Palindromes.Count <> 0 then      begin        Palindromes.Sort;        I := 0;        while I < Palindromes.Count do          begin            s := Palindromes[I];          Memo1.Append(s);            repeat              Inc(I);          until (I >= Palindromes.Count) or (Palindromes[I] <> s);          end;      end    else      Memo1.Append('no palindromes');  finally  Palindromes.Free;  end;end; But it could also look like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);  function ToLower(const s: string): string; begin ToLower := LowerCase(s) end;  function Fits(const s: string): Boolean; begin Fits := (s.Length > 2) and (s = ReverseString(s)) end;  function Less(const L, R: string): Boolean; begin Less := L < R end;  function Join(const L, R: string): string; begin Join := R + LineEnding + L end;begin  Memo1.Append(Memo1.Lines.Text    .Words([#9, #10, #13, ' ', ',', ';'])    .Map(@ToLower)    .Select(@Fits)    .Distinct(@Less)    .Fold(@Join)    .OrElse('no palindromes')  );end;  And, of course, everyone can have their own opinion, which of the solutions is more readable and simpler. ---------------------------------------------------------------------- engkin · 2021-03-29 17:05 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     .OrElse('no palindromes') ---------------------------------------------------------------------- Martin_fr · 2021-03-29 18:36 With the appropriate helpers, I think the original code boils down to (not tested): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  &Word: String;begin  LastWord := '';  for &Word in      lowercase(Memo1.Lines.Text)     .Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty)     .Sort // could be a normal function, "for word in sort(memo....split) do"  do  begin    If &Word = LastWord then continue;    LastWord := &Word;    if (&Word.Length <=2) or (&Word <> ReverseString(&Word))  then continue;     Memo1.Append(&Word);  end;  IMHO: Ugly, with all that is going into the for loop header.... ---------------------------------------------------------------------- engkin · 2021-03-29 19:11 The class: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses  LazUTF8, StrUtils, Generics.Collections; type  TWordSet = specialize TSortedHashSet;   TPalindromeSet = class(TWordSet)    function Add(constref AValue: String): Boolean; override;  end; function TPalindromeSet.Add(constref AValue: String): Boolean;begin  if (UTF8Length(AValue)>2) and (AValue = UTF8ReverseString(AValue)) then  Result:=inherited Add(AValue)  else    Result:=False;end;  Usage: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  Palindromes: TPalindromeSet;  Words: TStringArray;  AWord: string;begin  Words := Memo1.Lines.Text           .ToLower           .Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty);   Palindromes := TPalindromeSet.Create();  try    for AWord in Words do      Palindromes.Add(AWord);     for AWord in Palindromes do      Memo1.Append(AWord);     if Palindromes.Count=0 then      Memo1.Append('no palindromes');  finally    SetLength(Words, 0);    Palindromes.Free;  end;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;N AN;FAT;BAAB;FAAF;باب;文言文 ---------------------------------------------------------------------- Pluto · 2021-03-30 02:44 Thank you.  Well done. ---------------------------------------------------------------------- Blade · 2021-03-30 04:41 Quote from: Pluto on March 28, 2021, 07:36:15 pmI'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 (Functional programming in Pascal) QuoteSymbolicFrank What is the problem that anonymous functions solve? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function IsNegative(SomeParam: Integer): Boolean;var  SomeVar: Integer;   function Calculate: Boolean;  begin    Result := False;    if SomeVar < 0 then Result := True;  end; begin  Result := Calculate;end; ---------------------------------------------------------------------- Leledumbo · 2021-03-30 07:15 Quote from: Blade on March 30, 2021, 06:41:20 amCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function IsNegative(SomeParam: Integer): Boolean;var  SomeVar: Integer;   function Calculate: Boolean;  begin    Result := False;    if SomeVar < 0 then Result := True;  end; begin  Result := Calculate;end; 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. ---------------------------------------------------------------------- PascalDragon · 2021-03-30 07:34 Quote from: Martin_fr on March 29, 2021, 08:36:35 pmWith the appropriate helpers, I think the original code boils down to (not tested): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  &Word: String;begin  LastWord := '';  for &Word in      lowercase(Memo1.Lines.Text)     .Split([#9, #10, #13, ' ', ',', ';'], TStringSplitOptions.ExcludeEmpty)     .Sort // could be a normal function, "for word in sort(memo....split) do"  do  begin    If &Word = LastWord then continue;    LastWord := &Word;    if (&Word.Length <=2) or (&Word <> ReverseString(&Word))  then continue;     Memo1.Append(&Word);  end;  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. ---------------------------------------------------------------------- lainz · 2021-03-30 14:51 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. ---------------------------------------------------------------------- Leledumbo · 2021-03-30 21:57 Quote from: lainz on March 30, 2021, 04:51:38 pmLately 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. ---------------------------------------------------------------------- avk · 2021-03-31 03:41 Quote from: lainz on March 30, 2021, 04:51:38 pmLately 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.