### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: Functional type with both 'is nested' and 'of object'  (Read 1626 times)

#### trexet

• New Member
• Posts: 32
##### Functional type with both 'is nested' and 'of object'
« on: June 19, 2024, 06:01:08 pm »
Hi. Is it possible to make a functional type for a function nested inside a class method?

{\$modeswitch nestedprocvars} is on.

Code: Pascal  [Select][+][-]
1. TWaitForConditionNested = function(): Boolean of object is nested;
Quote
uwait.pas(13,61) Error: Syntax error, ";" expected but "is" found

Code: Pascal  [Select][+][-]
1. TWaitForConditionNested = function(): Boolean of object; is nested;
Quote
uwait.pas(13,62) Error: Syntax error, "IMPLEMENTATION" expected but "is" found

Code: Pascal  [Select][+][-]
1. TWaitForConditionNested = function(): Boolean is nested of object;
Quote
uwait.pas(13,61) Error: Syntax error, ";" expected but "OF" found

« Last Edit: June 19, 2024, 06:03:38 pm by trexet »

#### Khrys

• New Member
• Posts: 36
##### Re: Functional type with both 'is nested' and 'of object'
« Reply #1 on: June 20, 2024, 08:26:10 am »
According to the syntax diagram in the reference guide's section on procedural types (https://www.freepascal.org/docs-html/ref/refse17.html) they are mutually exclusive modifiers.

The entire reason these two modifiers exist in the first place is to capture the implicit changes to the function signature of methods and nested procedures.
Namely, object methods have the  Self  pointer as their actual first argument, making the following technically equivalent:
Code: Pascal  [Select][+][-]
1. function TFoo.Bar(Alpha: Integer): Boolean;                     // Method
2. function TFoo_Bar(const Self: TFoo; Alpha: Integer): Boolean;   // Free-standing function

The special thing about nested procedures is that they can access the enclosing function's local variables & parameters. This is made possible by passing the parent function's frame pointer (describing the general location of local variables) as the first parameter:
Code: Pascal  [Select][+][-]
1. function BarOuter(Alpha: Integer): Boolean;
2.   function BarInner(): Boolean;                                 // Nested;        Can access "Alpha"
3. function BarOuter_BarInner(const ParentFP: Pointer): Boolean;   // Free-standing; Resolves "Alpha" by adding an offset to "ParentFP"

This single requirement (the ability to access the enclosing function's local variables and parameters) is enough to make nested procedures work inside methods, because  Self  is just another parameter/local variable of the parent function; there's no need to explicitly combine these two modifiers -  is nested;  is enough.

Knowing all this, we can demonstrate why  {\$modeswitch nestedprocvars}  can be a bad idea if one isn't careful enough when passing around such function pointers. The crux lies in the loss of type & memory safety when accessing parent local variables (implicitly through  ParentFP); the following code compiles without any warnings and demonstrates why nested procedure variables aren't closures - surrounding variables are not captured:
Code: Pascal  [Select][+][-]
1. program NestedProcedureVariableAbuse;
2.
3. {\$mode objfpc}
4. {\$modeswitch nestedprocvars}
5.
6. type
7.   TNestedBar = function(X: Integer): Integer is nested;
8.
9. function GoodBar(): TNestedBar;
10.   function Bar(X: Integer): Integer;  // Does not use parent's local variables - OK
11.   begin
12.     Exit(X + 8);
13.   end;
14. begin
15.   Result := @Bar;
16. end;
17.
19. var
20.   Alpha: Integer = 8;
21.   function Bar(X: Integer): Integer;  // Uses parent's local variables - DANGEROUS
22.   begin                               // "Alpha" is equivalent to PInteger(ParentFP)[ARBITRARY_OFFSET]
23.     Exit(X + Alpha);                  // Optimizations WILL break this in most other contexts
24.   end;
25. begin
26.   Result := @Bar;
27. end;
28.
29. procedure Main();
30. begin
31.   WriteLn(GoodBar()(42)); // Outputs "50" as expected
32.   WriteLn(BadBar()(42));  // Undefined behaviour; in fact doesn't even work when there's a local Integer variable defined
33. end;

In essence, procedural function variables are fine to be passed around and called as long as they don't use parent local variables - otherwise they become incredibly fragile and dangerous. Nowadays function references (https://forum.lazarus.freepascal.org/index.php?topic=59468.0) are the way to go in these cases.

#### PascalDragon

• Hero Member
• Posts: 5576
• Compiler Developer
##### Re: Functional type with both 'is nested' and 'of object'
« Reply #2 on: June 23, 2024, 05:05:28 pm »
What exactly is it that you're trying to solve? Can you give a simple example? 🤔

#### trexet

• New Member
• Posts: 32
##### Re: Functional type with both 'is nested' and 'of object'
« Reply #3 on: June 24, 2024, 01:05:46 am »
What exactly is it that you're trying to solve? Can you give a simple example? 🤔

Consider a simple wait_until implementation:
Code: Pascal  [Select][+][-]
1. type
2.     TWaitForCondition = function(): Boolean of object;
3.
4. procedure WaitFor(F: TWaitForCondition);
5. begin
6.     while (not F()) do begin
7.         Application.ProcessMessages();
8.         Sleep(SleepInterval);
9.     end;
10. end;

Being used like this:
Code: Pascal  [Select][+][-]
1. procedure TListControlFrame.WaitRefresh();
2. begin
3.     Screen.Cursor := crHourGlass;
4.     ListBoxFilter.Items.Assign(PStringList^);
5.     ListBoxFilter.InvalidateFilter();
6.     WaitFor(@fFilterWaitCondition); // <---- HERE
7.     Screen.Cursor := crDefault;
8. end;

Where wait condition is

Code: Pascal  [Select][+][-]
1. function TListControlFrame.fFilterWaitCondition(): Boolean;
2. begin
3.     Result := not ListBoxFilter.IdleConnected;
4. end;

ListBoxFilter.InvalidateFilter() starts an async operation, which I should synchronize to by checking property ListBoxFilter.IdleConnected.

What I'm trying to achieve is to make fFilterWaitCondition() nested inside WaitRefresh(), because it's the only user of fFilterWaitCondition().
However, by being nested it becomes both nested and a (hidden?) class method.

I suppose solving this would be easier by using function references in 3.3.
« Last Edit: June 24, 2024, 01:07:24 am by trexet »

#### PascalDragon

• Hero Member
• Posts: 5576
• Compiler Developer
##### Re: Functional type with both 'is nested' and 'of object'
« Reply #4 on: June 25, 2024, 09:11:26 pm »
What I'm trying to achieve is to make fFilterWaitCondition() nested inside WaitRefresh(), because it's the only user of fFilterWaitCondition().
However, by being nested it becomes both nested and a (hidden?) class method.

With nested functions it will work correctly with nested functions as well as global functions. If you add an additional overload for methods you can cover all your bases:

Code: Pascal  [Select][+][-]
1. program tnested;
2.
3. {\$mode objfpc}{\$H+}
4. {\$modeswitch nestedprocvars}
5.
6. type
7.   TWaitForConditionFunc = function: Boolean is nested;
8.   TWaitForConditionMethod = function: Boolean of object;
9.
10.   TTest = class
11.     k: LongInt;
12.     function DoSomethingObj: Boolean;
13.     procedure Test;
14.   end;
15.
16. procedure WaitFor(aArg: TWaitForConditionMethod);
17. begin
18.   while not aArg() do begin
19.     Writeln('Spinning');
20.   end;
21.   Writeln('Done');
22. end;
23.
24. procedure WaitFor(aArg: TWaitForConditionFunc);
25. begin
26.   while not aArg() do begin
27.     Writeln('Spinning');
28.   end;
29.   Writeln('Done');
30. end;
31.
32. var
33.   j: LongInt;
34.
35. function DoSomethingElse: Boolean;
36. begin
37.   Dec(j);
38.   Result := j = 0;
39. end;
40.
41. function TTest.DoSomethingObj: Boolean;
42. begin
43.   Dec(k);
44.   Result := k = 0;
45. end;
46.
47. procedure TTest.Test;
48. var
49.   i: LongInt;
50.
51.   function DoSomething: Boolean;
52.   begin
53.     Dec(i);
54.     Result := i = 0;
55.   end;
56.
57. begin
58.   i := 3;
59.   WaitFor(@DoSomething);
60.   j := 5;
61.   WaitFor(@DoSomethingElse);
62.   k := 1;
63.   WaitFor(@DoSomethingObj);
64. end;
65.
66. var
67.   t: TTest;
68. begin
69.   t := TTest.Create;
70.   try
71.     t.Test;
72.   finally
73.     t.Free;
74.   end;
75. end.

I suppose solving this would be easier by using function references in 3.3.

Function references allow the usage of global functions, nested functions as well as methods.

#### trexet

• New Member
• Posts: 32
##### Re: Functional type with both 'is nested' and 'of object'
« Reply #5 on: July 01, 2024, 03:23:25 pm »
Thanks, works fine:

Code: Pascal  [Select][+][-]
1. TWaitForCondition = function(): Boolean of object;
2. TWaitForConditionNested = function(): Boolean is nested;
3.

Code: Pascal  [Select][+][-]
1. procedure TListControlFrame.WaitRefresh();
2.     function fFilterWaitCondition(): Boolean;
3.     begin
4.         Result := not ListBoxFilter.IdleConnected;
5.     end;
6. begin
7.     {...}
8.     WaitFor(@fFilterWaitCondition);

I think it would be nice to have it clarified in docs that is nested works for functions nested inside methods and of object is not needed (as nesting inside method is confusing about of object).