Recent

Author Topic: Why with allows assignment  (Read 17786 times)

440bx

  • Hero Member
  • *****
  • Posts: 4875
Re: Why with allows assignment
« Reply #135 on: January 08, 2025, 03:56:36 pm »
Do you really believe your logical fallacies of not knowing the difference between implication and equivalence get less wrong when I read them again?

Like all claims you made were provably wrong
As I previously stated: Do the world a favor, stop posting your garbage.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

lainz

  • Hero Member
  • *****
  • Posts: 4649
  • Web, Desktop & Android developer
    • https://lainz.github.io/
Re: Why with allows assignment
« Reply #136 on: January 08, 2025, 04:01:54 pm »
But then
Code: Pascal  [Select][+][-]
  1. Foo().b := 1; {or} if foo().b = 1 then

There is no "with" but if that creates a temp var, then that means a temp var was created for operating on the result of the call to foo.
From that again we should IMHO then say that "with foo do" is just operating on the result, and that therefore it is not the "with" but the  for operating on the result that creates the temp var. Still agreed?

Hi, good explanation, that can apply to this the same?

Code: Pascal  [Select][+][-]
  1. with TStringList.Create do
  2. begin
  3.   Free;
  4. end;

Because I don't know really...

I think is the same, because TStringList.Create created the variable, and with only operates that..

Edit: also in my previous example
https://forum.lazarus.freepascal.org/index.php/topic,69755.msg542669.html#msg542669

It has sense now, because it doesn't create the variable, but uses it, so the values are no stepped on..
« Last Edit: January 08, 2025, 04:19:22 pm by lainz »

Warfley

  • Hero Member
  • *****
  • Posts: 1834
Re: Why with allows assignment
« Reply #137 on: January 08, 2025, 04:36:56 pm »
Now I am intrigued. Is that so? (And I don't actually know, so that is an open question).
But, I suspect it may not... Yet, it may even depend on some unclear parts in that statement.


First of all, it should be easy for you to show, that this is the case with the FPC compiler (at a version of your choice, with settings of your choice).
- Provide the Pascal code for which this happens
- Provide the fpc version and command line args
- Provide the generate asm with register allocation  -alr

It's in the node tree. Looking at the fpc source:
Code: Pascal  [Select][+][-]
  1.             if (hp.nodetype=loadn) and
  2.                (
  3.                 (tloadnode(hp).symtable=current_procinfo.procdef.localst) or
  4.                 (tloadnode(hp).symtable=current_procinfo.procdef.parast) or
  5.                 (tloadnode(hp).symtable.symtabletype in [staticsymtable,globalsymtable])
  6.                ) and
  7.                { MacPas objects are mapped to classes, and the MacPas compilers
  8.                  interpret with-statements with MacPas objects the same way
  9.                  as records (the object referenced by the with-statement
  10.                  must remain constant)
  11.                }
  12.                not(is_class(hp.resultdef) and
  13.                    (m_mac in current_settings.modeswitches)) then
  14.               begin
  15.                 { simple load, we can reference direct }
  16.                 refnode:=p;
  17.               end
  18.             else
  19.               begin
  20.                 { complex load, load in temp first }
  21.                 newblock:=internalstatements(newstatement);
  22.                 { when we can't take the address of p, load it in a temp }
  23.                 { since we may need its address later on                 }
  24.                 if not valid_for_addr(p,false) then
  25.                   begin
  26.                     calltempnode:=ctempcreatenode.create(p.resultdef,p.resultdef.size,tt_persistent,true);
  27.                     addstatement(newstatement,calltempnode);
  28.                     addstatement(newstatement,cassignmentnode.create(
  29.                         ctemprefnode.create(calltempnode),
  30.                         p));
  31.                     p:=ctemprefnode.create(calltempnode);
  32.                     typecheckpass(p);
  33.                   end;
  34.  

So the with statement distinguishes between two cases. First with over some expression which is just a load of an existing object, so some existing memory address. In this case with is literally just a macro for the load of this object (refnode:=p). If it's not, it is checked if the object returned by the expression can be taken the address from, i.e. if the object has a persistent memory location. If that is also not the case, a new tempnode will be created (which can be either a memory location or a register), and the result of the expression is written into it.

You can see that when compiling the following test program:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = record
  3.     i: Integer;
  4.   end;
  5.  
  6. function Foo: TTest;
  7. begin
  8. end;
  9.  
  10. begin
  11.   With Foo do
  12.     i:=42;
  13. end.

With the switch -vp to generate the tree.log, which prints the internal node tree. Looking at the nodes right before the code generation step (so after most optimizations):
Code: Pascal  [Select][+][-]
  1.          (statementn, resultdef = <nil>, pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 255
  2.             (tempcreaten, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 0
  3.                size = 2, temptypedef = TTest = "<record type>", tempinfo = $3F9BB6E8
  4.                [ti_addr_taken]
  5.                tempinit =
  6.                nil
  7.             )
  8.  
  9.          )
  10.          (statementn, resultdef = <nil>, pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 255
  11.             (assignn, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 255
  12.                (temprefn, resultdef = TTest = "<record type>", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_REF, flags = [nf_pass1_done,nf_write], cmplx = 1
  13.                   temptypedef = TTest = "<record type>", (tempinfo = $3F9BB6E8 flags = [ti_addr_taken])
  14.                )
  15.                (calln, resultdef = TTest = "<record type>", pos = (11,8), loc = LOC_INVALID, expectloc = LOC_REG, flags = [nf_pass1_done], cmplx = 255
  16.                   proc = Foo:<record type>;
  17.                )
  18.             )
  19.  
  20.          )
We see first a tempcreatenode which creates a new temporary object of type 'TTest = "<record type>"' and it is later assigned the resullt of the call to the function Foo.
Infact the fpc generates two temporaries, because the next part in the node tree is:
Code: Pascal  [Select][+][-]
  1.             (tempcreaten, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 0
  2.                size = 4, temptypedef = <no type symbol> = "^TTest", tempinfo = $3F9BB7A8
  3.                [ti_may_be_in_reg]
  4.                tempinit =
  5.                nil
  6.             )
  7.  
  8.          )
  9.          (statementn, resultdef = <nil>, pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [], cmplx = 4
  10.             (assignn, resultdef = $void = "untyped", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_VOID, flags = [nf_pass1_done], cmplx = 1
  11.                (temprefn, resultdef = <no type symbol> = "^TTest", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_CREG, flags = [nf_pass1_done,nf_write], cmplx = 1
  12.                   temptypedef = <no type symbol> = "^TTest", (tempinfo = $3F9BB7A8 flags = [ti_may_be_in_reg])
  13.                )
  14.                (addrn, resultdef = <no type symbol> = "^TTest", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_REG, flags = [nf_pass1_done,nf_internal], cmplx = 1, addrnodeflags = [anf_typedaddr]
  15.                   (temprefn, resultdef = TTest = "<record type>", pos = (11,12), loc = LOC_INVALID, expectloc = LOC_REF, flags = [nf_pass1_done,nf_address_taken], cmplx = 1
  16.                      temptypedef = TTest = "<record type>", (tempinfo = $3F9BB6E8 flags = [ti_addr_taken])
  17.                   )
  18.                )
  19.             )
This temporary object then contains the address to the first temporary object, which is then used for the actual dereferencing in the with statement.

The main point here is that in order to not having to re evaluate the with expression on each access, if it is not a memory object (but the result of a expression) a new temporary will be created and the result of the expression is loaded into that temporary.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10652
  • Debugger - SynEdit - and more
    • wiki
Re: Why with allows assignment
« Reply #138 on: January 08, 2025, 05:04:33 pm »
Hi, good explanation, that can apply to this the same?

Code: Pascal  [Select][+][-]
  1. with TStringList.Create do
  2. begin
  3.   Free;
  4. end;

Is the same as:
Code: Pascal  [Select][+][-]
  1. TStringList.Create.Free
So: Yes.

Of course without "with", that temp var can only be used for one further statement.





It's in the node tree. Looking at the fpc source:

There is a fundamental difference between. "With creates the temp variable" and "The compilers implementation for with has code that creates a temp variable."

I am sure that, if you go through the code of the compiler you can find a lot of things that are done within code for something else.

In that case, "with" is one of many ways to operate on the result of a function call. So (a copy of) code that is needed for that is included in the implementation of "with".

Reminder, your statement was
2. With doesn't create a temporary object
Provably wrong with the fpc code creating a tempnode

You did not say "the fpc compiler implements a copy of the code for operate on the result in the code handling with".

You said, that it was the "with" itself that creates the temp variable.


I will give you that, using "with" probably always constituted operating on the result. Therefore using "with" will always include all the effects that operating on the result has. But afaik, it is operating on the result that creates the temp var.



If I said "rounding a floating number creates a new stackframe" most people would (rightly) say that I am wrong.

Of course
Code: Pascal  [Select][+][-]
  1. b := round(a);
calls a function, and calling a function creates a stackframe. But that doesn't mean that the statement above is suddenly correct.

The statement tells a partial true, in such a way that the reader will be led to wrong conclusions. That makes it a false statement.

==> Just to say, if round ends up an intrinsic => chose any other function that actually gets called.

BrunoK

  • Hero Member
  • *****
  • Posts: 639
  • Retired programmer
Re: Why with allows assignment
« Reply #139 on: January 08, 2025, 05:21:52 pm »
Given the following program :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. { Int64 to demonstrate that the returned record is effectively copied to
  4.   the local stack space }
  5. type
  6.   RTest = record
  7.     V0: int64;
  8.     V1: int64;
  9.   end;
  10.   PRtest = ^RTest;
  11.  
  12.   { TTest }
  13.  
  14.   TTest = class
  15.     V0: int64;
  16.     V1: int64;
  17.     function GetSelfPtr: TTest;
  18.     property SelfInst: TTest read GetSelfPtr;
  19.     constructor Create;
  20.   end;
  21.  
  22.   function FooRec: RTest; forward;
  23.   function FooInst: TTest; forward;
  24.  
  25. var
  26.   vRec: RTest;
  27.   vInst: TTest = nil;
  28.  
  29.   function FooRec: RTest;
  30.   begin
  31.     Result := vRec;
  32.   end;
  33.  
  34.   function FooInst: TTest;
  35.   begin
  36.     if not Assigned(vInst) then
  37.       vInst := TTest.Create;
  38.     Result := vInst;
  39.   end;
  40.  
  41.   { TTest }
  42.  
  43.   function TTest.GetSelfPtr: TTest;
  44.   begin
  45.     Result := Self;
  46.   end;
  47.  
  48.   constructor TTest.Create;
  49.   begin
  50.     V0 := -11;
  51.     V1 := -11;
  52.   end;
  53.  
  54. begin
  55.   { Init record vRec }
  56.   with vRec do begin
  57.     V0 := -1;
  58.     V1 := -2;
  59.   end;
  60.  
  61.   { Where program variables are stored }
  62.  
  63.   WriteLn('With Record', LineEnding, '===========');
  64.   WriteLn(HexStr(UIntPtr(@vRec), SizeOf(Pointer) * 2), ' ', vRec.V0, ' ', vRec.V1);
  65.  
  66.   with FooRec do begin
  67.     // FooRec returns a pointer used to copy record to "with" cached record
  68.     V0 := 1;
  69.     v1 := 2;
  70.     WriteLn(V0, ' ', V1);
  71.     with PRtest(@V0)^ do
  72.       WriteLn(HexStr(UIntPtr(@V0), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  73.   end;
  74.   { Show we didnt alter the record vRec }
  75.   with vRec do
  76.     WriteLn(HexStr(UIntPtr(@vRec.V0), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  77.  
  78.   WriteLn('With Class instance',
  79.     LineEnding, '===================');
  80.   WriteLn(HexStr(UIntPtr(@vInst), SizeOf(Pointer) * 2), ' ',
  81.     HexStr(UIntPtr(@PPointer(vInst)^), SizeOf(Pointer) * 2));
  82.   with FooInst do begin
  83.     // FooRec returns a pointer used to copy record to "with" cached record
  84.     V0 := 11;
  85.     v1 := 12;
  86.     WriteLn(V0, ' ', V1);
  87.     with SelfInst do
  88.       WriteLn(HexStr(UIntPtr(SelfInst), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  89.   end;
  90.   { Show we worked on the instance returned FooInst and altered vInst itsel via
  91.     the returned TObject }
  92.   with vInst do
  93.     WriteLn(HexStr(UIntPtr(vInst), SizeOf(Pointer) * 2), ' ',
  94.       HexStr(UIntPtr(PPointer(vInst)^), SizeOf(Pointer) * 2), ' ', V0, ' ', V1);
  95.  
  96.   ReadLn;
  97.  
  98.   vInst.Free;
  99. end.
Compiled for x86_64, O1:
At line 66 with FooRec do begin the compiler generates
000000010000186B 488D4DE0                 lea rcx,[rbp-$20]
000000010000186F E86CFDFFFF               call -$00000294    # $00000001000015E0 FooRec project1.lpr:30
0000000100001874 488B45E0                 mov rax,[rbp-$20]
0000000100001878 488945F0                 mov [rbp-$10],rax
000000010000187C 488B45E8                 mov rax,[rbp-$18]
0000000100001880 488945F8                 mov [rbp-$08],rax
that is, it calls the FooRec function that returns a pointer to the record which is then FULLY copied to the local stack (not a pointer !) because it cannot store it in a register. 

At line 82 with FooInst do begin the compiler generates :
0000000100001AF1 E81AFBFFFF               call -$000004E6    # $0000000100001610 FooInst project1.lpr:35
0000000100001AF6 4889C3                   mov rbx,rax
0000000100001AF9 4889DE                   mov rsi,rbx
Here the Instance address value is not stored on the stack because it can be kept (cached, or so I suppose) in a register (rsi in this case) for the scope of the with. Note that a object instance is just a pointer.


Warfley

  • Hero Member
  • *****
  • Posts: 1834
Re: Why with allows assignment
« Reply #140 on: January 08, 2025, 05:34:07 pm »
There is a fundamental difference between. "With creates the temp variable" and "The compilers implementation for with has code that creates a temp variable."
But the temporary variable is one of the key features of with. It is missing in the documentation of with (it currently only mentions variable expressions), but the fact that if the expression is non variable, a local copy of the result of that expression is created is really important to the semantics of with. It has two major effects:
1. The expression in with will always be evaluated, even if it is never used:
Code: Pascal  [Select][+][-]
  1. function Foo: TTest;
  2. begin
  3.   WriteLn('Foo');
  4. end;
  5.  
  6. begin
  7.   With Foo do
  8.     WriteLn('Bar');
  9. end.
Even though the result of Foo is never accessed in the with, it is still executed. Also through the temporary object it is guaranteed that Foo will only be executed exactly once, independently how many accesses there are to the result. The fact that with creates a local copy of any non variable statement, is an essential part of the with semantic. It's the reason why the with MyClass.Create do try ... finally Free end; works.
With over a complex term creates a temporary copy of the result of that term to operate on. With ensures that the with expression is a variable, if it already is, it does nothing if it is a complex expression it creates a new temporary variable to operate on

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10652
  • Debugger - SynEdit - and more
    • wiki
Re: Why with allows assignment
« Reply #141 on: January 08, 2025, 05:47:44 pm »
There is a fundamental difference between. "With creates the temp variable" and "The compilers implementation for with has code that creates a temp variable."
But the temporary variable is one of the key features of with. It is missing in the documentation of with (it currently only mentions variable expressions), but the fact that if the expression is non variable, a local copy of the result of that expression is created is really important to the semantics of with. It has two major effects:
1. The expression in with will always be evaluated, even if it is never used:

The compiler could guarantee the "always eval", even without storing the result. (Still the difference between "with creates" and "the compiler does in its current implementation" => you said the former, but then tried to argue with the latter)

So, if we had
Code: Pascal  [Select][+][-]
  1. with foo() do {empty};
  2. with foo() do begin {empty} end;

Then for the correct functionality of "with" the temp var is not needed. If that optimization is lacking from the current implementation, then that still only allows a statement on the "implementation of with", but does not make it true to say that "with creates a temp var".



Besides, you did not show the complete code of the compiler. You showed code that creates a node for that temp var, but what about any code that later removes it (or the effects of it) again? So that looking at the compiler as a whole at the end of generating code for "with" there may not ("not always") be a temp var?

Feels like cheating? Because any optimizer business is a general business and not related to to "with" in particular? Well, its in line of saying the general "operating on the result" is not general when it happens within a "with" block. Or is it not?

Warfley

  • Hero Member
  • *****
  • Posts: 1834
Re: Why with allows assignment
« Reply #142 on: January 08, 2025, 10:37:18 pm »
Besides, you did not show the complete code of the compiler. You showed code that creates a node for that temp var, but what about any code that later removes it (or the effects of it) again? So that looking at the compiler as a whole at the end of generating code for "with" there may not ("not always") be a temp var?

Feels like cheating? Because any optimizer business is a general business and not related to to "with" in particular? Well, its in line of saying the general "operating on the result" is not general when it happens within a "with" block. Or is it not?
Well then the question becomes what is a temporary object? Some memory object that's in the assembly? Does a register count? This discussion gets quite philosophical. So why not look at the context of my posts and what I actually described when saying this.
Well my posts only ever concerned the internal representation in the FPC, so in the node tree which represents the semantic of the program. With "object" I specifically meant an persistent data representation in the node tree (as I clarified multiple times in this thread already).

Because optimizations are just semantic preserving transformations, but the semantic is defined in the node tree. So if the node tree creates a temporary object it means semantically there is this object. Then optimizations can decide if the same semantic can be preserved by removing that object later on.
But that's why there is the distinction between the Frontend, which encodes the semantic of a program in the AST (node tree), and the backend which then translates this semantic into machine vode
« Last Edit: January 08, 2025, 11:18:38 pm by Warfley »

TBMan

  • New Member
  • *
  • Posts: 13
Re: Why with allows assignment
« Reply #143 on: January 09, 2025, 01:25:39 am »
The syntax is a little crazy. I'd do this instead

Type

Point_type = record
   X, y: integer;
End;

Var
Point: point_type;

Function test(a,b:integer):integer;
Begin
     Test := a+b;
End;

Begin
With point do
 Begin
     x := test(100,5);
    y := test(5,5);
 End;
End.

You have to code things in a way to improve readabilty so if you
have to go back a week, or a month later you won't be staring at
headlights like a deer.
« Last Edit: January 09, 2025, 01:27:54 am by TBMan »

jamie

  • Hero Member
  • *****
  • Posts: 6781
Re: Why with allows assignment
« Reply #144 on: January 09, 2025, 01:39:20 am »
its handy when you want to set a bunch of properties deep within a class, for example the brush values of a canvas which lives in a form or some graphics control.

Example
 
Code: Pascal  [Select][+][-]
  1.  
  2.  With Form1.Canvas.Brush do
  3.  Begin
  4.    style =...
  5.    color =....
  6. end;
  7.  
The only true wisdom is knowing you know nothing

PascalDragon

  • Hero Member
  • *****
  • Posts: 5796
  • Compiler Developer
Re: Why with allows assignment
« Reply #145 on: January 09, 2025, 10:05:51 pm »
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.  Classes, SysUtils;
  5.  
  6. type
  7.  
  8.   TRecAB = record
  9.     A: integer;
  10.     B: integer;
  11.   end;
  12.  
  13.   { TMyObj }
  14.  
  15.   TMyObj = class
  16.   private
  17.     TRecAB: TRecAB;
  18.   public
  19.     property rec: TRecAB read TRecAB write TRecAB;
  20.   end;
  21.  
  22. var
  23.   Obj: TMyObj;
  24.  
  25. begin
  26.   Obj := TMyObj.Create;
  27.  
  28.   with Obj.rec do
  29.   begin
  30.     A := 10;
  31.     B := 20;
  32.   end;
  33.  
  34.   with Obj.rec do
  35.   begin
  36.     A := 15;
  37.   end;
  38.  
  39.   writeln(Obj.rec.A);
  40.   writeln(Obj.rec.B);
  41.   readln;
  42.  
  43. end.

A is 15 and B is 20 at the end. So it works fine for me...

Please note that this only works as long as the property uses field getter and setter. It will fail as soon as the getter is also a method. And how the property is implemented is not always in control of the developer (e.g. when using third party code). Which is another reason why Delphi decided to forbid this and FPC will follow suit sooner or later.

that FPC allows assigning a value to a function _outside_ the function itself, must be a "misnomer" too.
if you mean the OP program (revised) :
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. const
  4.   HighInt64 = int64(int64(-1) shr 1); // Typed const
  5.  
  6. type
  7.   TTest = record
  8.     V: int64;
  9.   end;
  10.  
  11. var
  12.   X: TTest;
  13.  
  14.   function Test: TTest;
  15.   begin
  16.     Result := X;
  17.   end;
  18.  
  19. var
  20.   i: qword;
  21. begin
  22.   with Test do begin
  23.     V := HighInt64; //No Error
  24.     WriteLn(X.V, ' ', {With Test.}V);
  25.   end;
  26.   Readln;
  27. end.
There is no assignment of a function outside it. The assignment is to the temporary TTest record returned by the Test function call. As my little snippet of code shows, the X.V record field is not changed, only the returned temporary record is altered to HighInt64.
Wrong and unacceptable!  The temporary represents the result of the function therefore it must be treated exactly the same as the result of the function.

The _correct_ way to do what that code atrocity above is doing is to assign the function result to a variable and then change the variable but, of course, that's too much work and worse, it is correct, we can't possibly have that.  In the code you present, V represents the result of the function therefore it MUST be treated as the function's result.

Assign a value to function results outside the function ?... no problem... assign values to constants ?... no problem...   it's all great as long as it's convenient... that's what really matters... correctness ?.... nah... who could possibly need that ?  (just in case, rhetorical.)

That's the whole original point of this thread: Delphi nowadays forbids changing fields of a record that is a return value of a function to matter if with is used or not. Older Delphi versions did not, just like older FPC versions or TP did not. FPC nowadays forbids it directly for the function result, but not yet if the function result is captured in a with-statement. But that is only because that is simply not implemented in FPC, because it requires explicit code to forbid this and doesn't stem from the language itself.

The syntax is a little crazy. I'd do this instead

Type

Point_type = record
   X, y: integer;
End;

Var
Point: point_type;

Function test(a,b:integer):integer;
Begin
     Test := a+b;
End;

Begin
With point do
 Begin
     x := test(100,5);
    y := test(5,5);
 End;
End.

You have to code things in a way to improve readabilty so if you
have to go back a week, or a month later you won't be staring at
headlights like a deer.

Speaking of readability: please use [code=pascal][/code] to improve readabililty and to avoid the forum software interpreting the code.

440bx

  • Hero Member
  • *****
  • Posts: 4875
Re: Why with allows assignment
« Reply #146 on: January 10, 2025, 12:11:04 am »
Delphi nowadays forbids changing fields of a record that is a return value of a function to matter if with is used or not. Older Delphi versions did not, just like older FPC versions or TP did not. FPC nowadays forbids it directly for the function result, but not yet if the function result is captured in a with-statement. But that is only because that is simply not implemented in FPC, because it requires explicit code to forbid this and doesn't stem from the language itself.
I just want to make sure I am interpreting the above corretly.

Does the above mean that FPC will "eventually" forbid "changing fields of a record that is a return value of a function to matter if with is used or not." ?

A "yes" answer would definitely be encouraging.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

egsuh

  • Hero Member
  • *****
  • Posts: 1520
Re: Why with allows assignment
« Reply #147 on: January 10, 2025, 03:38:01 am »
Anyway whether to use "with" or not is programmers' choice, while directly changing record-type function result had better be avoided.

egsuh

  • Hero Member
  • *****
  • Posts: 1520
Re: Why with allows assignment
« Reply #148 on: January 11, 2025, 06:54:15 am »
I have no intention of re-igniting debates here, but I made a simple test, both with record and object. Both seem to work correctly.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeSwitch advancedRecords}
  5.  
  6. interface
  7.  
  8. uses
  9.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  10.  
  11. type
  12.  
  13.   { RRec }
  14.  
  15.   RRec = record
  16.   private
  17.     function getString: string;
  18.     procedure setString(AValue: string);
  19.   public
  20.     min, max : integer;
  21.     property AsString: string read getString write setString;
  22.   end;
  23.  
  24.   TRec = object
  25.   private
  26.     function getString: string;
  27.     procedure setString(AValue: string);
  28.   public
  29.     min, max : integer;
  30.     property AsString: string read getString write setString;
  31.   end;
  32.  
  33.   { TForm1 }
  34.  
  35.   TForm1 = class(TForm)
  36.     Button1: TButton;
  37.     Button2: TButton;
  38.     procedure Button1Click(Sender: TObject);
  39.     procedure Button2Click(Sender: TObject);
  40.   private
  41.  
  42.   public
  43.  
  44.   end;
  45.  
  46. var
  47.   Form1: TForm1;
  48.  
  49. function GetRange : RRec;
  50. function GetRangeObj: TRec;
  51.  
  52. implementation
  53.  
  54. function GetRange: RRec;
  55. begin
  56.  
  57. end;
  58.  
  59. function GetRangeObj: TRec;
  60. begin
  61.  
  62. end;
  63.  
  64. {$R *.lfm}
  65.  
  66. { RRec }
  67.  
  68. function RRec.getString: string;
  69. begin
  70.   Result := Format('%d..%d', [min, max]);
  71. end;
  72.  
  73. procedure RRec.setString(AValue: string);
  74. var
  75.   tempres : TStringArray;
  76. begin
  77.   tempres := AValue.Split(['..']);
  78.   min := StrToInt(tempres[0]);
  79.   max := StrToInt(tempres[1]);
  80. end;
  81.  
  82. { RRec }
  83.  
  84. function TRec.getString: string;
  85. begin
  86.   Result := Format('%d..%d', [min, max]);
  87. end;
  88.  
  89. procedure TRec.setString(AValue: string);
  90. var
  91.   tempres : TStringArray;
  92. begin
  93.   tempres := AValue.Split(['..']);
  94.   min := StrToInt(tempres[0]);
  95.   max := StrToInt(tempres[1]);
  96. end;
  97.  
  98. { TForm1 }
  99.  
  100. procedure TForm1.Button1Click(Sender: TObject);
  101. begin
  102.    with GetRange do begin
  103.       AsString := '12..16';
  104.       ShowMessage(IntToStr(Min));
  105.    end;
  106. end;
  107.  
  108. procedure TForm1.Button2Click(Sender: TObject);
  109. begin
  110.   with GetRangeObj do begin
  111.      AsString := '12..16';
  112.      ShowMessage(IntToStr(Min));
  113.   end;
  114. end;
  115.  
  116. end.
  117.  

I have no interest at all in what compilers do nor what is the philosopy under the programming language development.

The difference between defining a variable (I can write as follows) and calling function is that variable may last long after their values are not needed anymore. In that sense, I think calling function with "with" whose result I can operate on is quite useful feature.

          var
               ARange: RRec;
          begin
               with ARange do begin
                      ....
               end;
          end; 

440bx

  • Hero Member
  • *****
  • Posts: 4875
Re: Why with allows assignment
« Reply #149 on: January 11, 2025, 07:36:12 am »
I think calling function with "with" whose result I can operate on is quite useful feature.
Writable constants are useful too.   Now, we have a language that allows assigning to a function result outside the function, it's useful, therefore... no problem!.

OTH, @PascalDragon's last post gives the impression that this "useful" feature may not be around in the future.  Of course, he is the only one who can confirm that but, if the feature isn't going to be around in the future, that really makes it "less useful".

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018