Recent

Author Topic: Const record parameters by default  (Read 29396 times)

Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #45 on: April 04, 2024, 11:26:15 am »
Look at this code from TFPGObjectList. Why did they need to add "const Item: T"? Did they think it would be faster to pass? If so why doesn't the compiler know how to do this for them?

Comparer functions are afaik often const if they can be used for strings or dynarrays. then there are optimization options with const.

I just learned this recently. I actually did just audit all my code in one project and made all strings const. That's part of the reason I brought this up. Usually in software if something is the common case you make that the default. Strings and ref counted types in particular have overhead passing them so choosing the slower option by default seems questionable from a compiler perspective.


EDIT: err, the other guy reminded me const on ref counted types doesn't increment the ref count so obviously can't be const by default, except for may single threaded programs maybe? If the ref count is incremented upon call then decremented after exit does that balance out on all single threaded programs anyways?

« Last Edit: April 04, 2024, 12:11:24 pm by Ryan J »

cdbc

  • Hero Member
  • *****
  • Posts: 2787
    • http://www.cdbc.dk
Re: Const record parameters by default
« Reply #46 on: April 04, 2024, 11:56:38 am »
Hi
<rant>
Hmmm, originally 'const' /in delphi/ came about, to save on copying strings in parameters and _that_ certainly makes sense(and you can notice the speed improvement). As documented, 'const' has no influence on ordinal parameter-types, this however, is _not_ the case with managed types; you have to be _very_ careful with e.g.: COM-interfaces passed with the 'const' modifier, because in that case the compiler __doesn't__ increment the ref-count!!!
'Const', like many other things, is to be used in moderate doses, _not_ 'cluelessly' shoved onto every parameter passed. When used correctly, it does a great job.
</rant>
You have it from one of the compiler/rtl devs,  that due to backwards compatibility, THIS WON'T BE CHANGED! It'd simply break too dang much!
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #47 on: April 04, 2024, 12:06:04 pm »
You have it from one of the compiler/rtl devs,  that due to backwards compatibility, THIS WON'T BE CHANGED! It'd simply break too dang much!
Regards Benny

Sorry I did a really poor job communicating this. I really just wanted to see what people think about the default being the more correct choice. FPC seems fossilized so I don't expect any breaking changes I was just curious what other programmers thought about the choices made 20 years ago, did they hold up?

440bx

  • Hero Member
  • *****
  • Posts: 6492
Re: Const record parameters by default
« Reply #48 on: April 04, 2024, 12:35:34 pm »
@Ryan,

The example you provided was useful. 

Here is the _critical_ characteristic of "const" to always keep in mind: "const" only applies to the identifier that was made "const".  In your example, it applies to the _parameter_ (r in this case) _and_ the compiler can only detect _direct_ conflicts.  It cannot detect indirect conflicts (the great majority of compilers can't, it's not just FPC or Pascal.)

consider this modified example based on your code:
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2.  
  3. {$MODESWITCH ADVANCEDRECORDS ON}
  4.  
  5. program test;
  6.  
  7. type
  8.   PMyRec = ^TMyRec;
  9.   TMyRec = record
  10.     x: Integer;  { note that "x" is not declared as "const" }
  11.  
  12.     procedure SetX(v: Integer);
  13.   end;
  14.  
  15. var
  16.   r : TMyRec;
  17.  
  18. procedure xSetX(v : integer);
  19. begin
  20.   r.x := v;
  21. end;
  22.  
  23. procedure TMyRec.SetX(v: Integer);
  24. begin
  25.   x := v;
  26. end;
  27.  
  28. var
  29.   p : PMyRec = nil;
  30.  
  31. procedure TestRec(const r: TMyRec);
  32. begin
  33.   r.SetX(100);             { indirection }
  34.   writeln(r.x);  { 100 }
  35.  
  36.   xSetX(200);              { indirection }
  37.   writeln(r.x);  { 200 }
  38.  
  39.   p := @r;                 { indirection }
  40.  
  41.   p^.x := 300;
  42.   writeln(r.x);  { 300 }
  43. end;
  44.  
  45. begin
  46.   TestRec(r);
  47.  
  48.   readln;
  49. end.                
In all cases, "r" is _not_ being assigned/modified _directly_ (which is what the compiler can detect), "r" is being modified indirectly and many, if not all, compilers would not be able to preserve the "const-ness" of the parameter.  Actually, just about any language that allows pointer manipulation could not prevent modification through a pointer.

A crucial fact in the Pascal code is that the "const" is applied to "r", it is NOT applied to "x" which is where the value is actually stored.  Some languages allow specifying "const" in the variable definition itself (just in case, I know of no language that allows mixing mutable and immutable in a record, IOW, no mixing allowed.)  When "const" is applied to the variable's storage then the compiler can enforce it with reasonable ease.  When "const" is applied to a parameter (instead of the storage) as done in Pascal then the compiler can only detect and enforce _direct_ attempts at changing the value.  That's normal because attempting to detect those cases would be unreliable and, in some cases, would create a level of complexity that is totally unreasonable, e.g, the parameter could be passed down 100, 1000, 10,000 procedures before it is modified and it would be totally absurd for _any_ compiler to attempt tracking changes across procedures.
 
"const" works as intended:  it prevents _direct_ modification, it does not prevent indirect modification.

If you feel like it, put this code above the "TMyRec.SetX" method,
Code: Pascal  [Select][+][-]
  1. Procedure Funproc(var p : TMyRec); begin end;
and add a call to Funproc in TestRec, you'll find that the compiler will not compile that code (which is as it should be.)  IOW, the compiler is doing its job as it should.

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: Const record parameters by default
« Reply #49 on: April 04, 2024, 01:27:16 pm »
Sorry I did a really poor job communicating this. I really just wanted to see what people think about the default being the more correct choice.
For me it just out of the question. I'm reusing byval local copies more often than not. Especially TPoint, TRect. Saves me extra declarations. And I'll prefer to tell the compiler about the parameter and not vice versa.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Joanna

  • Hero Member
  • *****
  • Posts: 1451
Re: Const record parameters by default
« Reply #50 on: April 04, 2024, 02:58:02 pm »
Quote
Sigh, instead of making the case for anything and giving insights you pull rank. I think I started using FPC in 2005 after CodeWarrior for macOS was abandoned. I even developed a number of compiler features. That doesn't matter though, any programmer of any language should be able to debate the merits for opt-in const or const by default.
It’s not a matter of “pulling rank”. I have nothing against the concept of people who actually use fpc Having intellectual curiosities about how it works or even critiquing it...

However I’ve had far too many unpleasant encounters with people who don’t even use FPC feigning interest in it only for the sake of drawing FPC users/developers into useless time wasting discussions in which they make impossible demands as to how it needs to be changed.

Quote
FPC seems fossilized so I don't expect any breaking changes I was just curious what other programmers thought about the choices made 20 years ago
so you are conducting a survey About “fossilized” fpc looking for possible malcontents to start a revolution ? This seems very counterproductive.  :(


Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #51 on: April 04, 2024, 03:19:19 pm »
Sorry I did a really poor job communicating this. I really just wanted to see what people think about the default being the more correct choice.
For me it just out of the question. I'm reusing byval local copies more often than not. Especially TPoint, TRect. Saves me extra declarations. And I'll prefer to tell the compiler about the parameter and not vice versa.

Just curious, what the nature of these functions? Could you post some examples? I'm scanning my code but nothing is jumping out at me.

Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #52 on: April 04, 2024, 03:23:19 pm »
Quote
FPC seems fossilized so I don't expect any breaking changes I was just curious what other programmers thought about the choices made 20 years ago
so you are conducting a survey About “fossilized” fpc looking for possible malcontents to start a revolution ? This seems very counterproductive.  :(

I was just curious what Pascal programmers think about const after using it for 5, 10+ years, did the original promise still hold up?

As for FPC being fossilized, virtually all new changes are adoptions from Delphi and backwards compatibility is always a top priority. It's not in the spirit of the developers to upturn old rules from Pascal, whether they agree or not. Just my observations. Even if they hated const I don't think they would remove or change it now.

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: Const record parameters by default
« Reply #53 on: April 04, 2024, 05:28:13 pm »
Just curious, what the nature of these functions? Could you post some examples? I'm scanning my code but nothing is jumping out at me.
Code: Pascal  [Select][+][-]
  1. procedure THallControl.DrawSeatGlyph(R: TRect; ACorner: Integer;
  2.   AGlyph: TGraphic);
  3. var
  4.   P: TPoint;
  5. begin
  6.   P := ScaleXY(AGlyph.Width, AGlyph.Height);
  7.   case ACorner of
  8.     1: // Bottom-Left
  9.         R := Rect(R.Left, R.Bottom - P.Y, R.Left + P.X, R.Bottom);
  10.     2: // Bottom-Right
  11.         R := Rect(R.Right - P.X, R.Bottom - P.Y, R.Right, R.Bottom);
  12.     3: // Top-Right
  13.         R := Rect(R.Right - P.X, R.Top, R.Right, R.Top + P.Y);
  14.     4: // Top-Left
  15.         R := Rect(R.Left, R.Top, R.Left + P.X, R.Top + P.Y);
  16.   end;
  17.   Canvas.StretchDraw(R, AGlyph);
  18. end;

Code: Pascal  [Select][+][-]
  1.   procedure DrawButtonCell(ARect: TRect; Enabled, Pushed: Boolean);
  2.   var
  3.     Details: TThemedElementDetails;
  4.     Det: TThemedButton;
  5.   begin
  6.     Dec(ARect.Right);
  7.     Dec(ARect.Bottom);
  8.     if Pushed or (gdPushed in aState) then Det := tbPushButtonPressed
  9.     else if gdHot in aState then Det := tbPushButtonHot
  10.     else if Enabled then Det := tbPushButtonNormal
  11.     else Det := tbPushButtonDisabled;
  12.     Details := ThemeServices.GetElementDetails(Det);
  13.     ThemeServices.DrawElement(VotesGrid.Canvas.Handle, Details, ARect, Nil);
  14.   end;  
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

runewalsh

  • Full Member
  • ***
  • Posts: 115
Re: Const record parameters by default
« Reply #54 on: April 04, 2024, 05:36:30 pm »
Btw, const parameters aren’t even necessarily good. From a low-level perspective, it’s harder to pass a record to a procedure by value, but easier to access from inside the procedure: copied records are accessed as stack pointer register + constant offset, while each of the records passed by reference consumes its own register, leaving fewer registers for useful work (and if they end up spilled, or simply passed through the stack from the beginning because there were too many parameters, they are stored as pointers and accessing their members starts requiring 2 memory accesses each: first read the pointer itself from the stack, then access through the pointer). Maybe for this reason e.g. the recommendation in C++ is to pass string_view (and small PODs in general) by value.

Just had an idea: default qualifier can be a property of the type. In this case, it won’t break any existing code (and might even be easy to implement: e.g. the problem with automatically constifying parameters not written by the implementation is that the compiler does not know the implementation at that point; but it knows the type).

Code: Pascal  [Select][+][-]
  1. type
  2.         StringView = record
  3.                 p: PChar;
  4.                 n: SizeUint;
  5.         end pass by value;
  6.  
  7.         StringHash = record
  8.                 h: array[0 .. 511] of string;
  9.         end pass by const;
  10.  
  11. function StringPresent(s: StringView; sh: StringHash): boolean; // automatically adds “const” to “sh”
  12.  

:D

VisualLab

  • Hero Member
  • *****
  • Posts: 731
Re: Const record parameters by default
« Reply #55 on: April 05, 2024, 10:23:03 am »
Just had an idea: default qualifier can be a property of the type. In this case, it won’t break any existing code (and might even be easy to implement: e.g. the problem with automatically constifying parameters not written by the implementation is that the compiler does not know the implementation at that point; but it knows the type).

Code: Pascal  [Select][+][-]
  1. type
  2.         StringView = record
  3.                 p: PChar;
  4.                 n: SizeUint;
  5.         end pass by value;
  6.  
  7.         StringHash = record
  8.                 h: array[0 .. 511] of string;
  9.         end pass by const;
  10.  
  11. function StringPresent(s: StringView; sh: StringHash): boolean; // automatically adds “const” to “sh”

Theoretically, this is a solution. But it has the disadvantage that in the place where the type is declared, additional information is provided about how to treat variables created on the basis of this type. However, where a variable of a specific type (routine) is used, there is no information on how to treat this variable. And in Pascal it was usually the other way around*), i.e. the type declaration did not contain information about how to treat variables in various situations, because information about the treatment of variables of a specific type was provided where these variables were to be used, e.g. as routine arguments. So the proposal given is inconsistent with what exists in Pascal.

---
*) Yes, I know, there are some inconsistencies in Pascal, but this problem should not be aggravated. Rather, the inconsistencies should be straightened out.

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: Const record parameters by default
« Reply #56 on: April 05, 2024, 10:43:02 am »
Just had an idea: default qualifier can be a property of the type. In this case, it won’t break any existing code (and might even be easy to implement: e.g. the problem with automatically constifying parameters not written by the implementation is that the compiler does not know the implementation at that point; but it knows the type).

Code: Pascal  [Select][+][-]
  1. type
  2.         StringView = record
  3.                 p: PChar;
  4.                 n: SizeUint;
  5.         end pass by value;
  6.  
  7.         StringHash = record
  8.                 h: array[0 .. 511] of string;
  9.         end pass by const;
  10.  
  11. function StringPresent(s: StringView; sh: StringHash): boolean; // automatically adds “const” to “sh”
  12.  

:D
IMHO the Ryan J example from post #43 can be resolved by introducing a const declaration modifier for methods, marking them as accessors (const Self), not mutators in a way that is done in C++ for example.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #57 on: April 05, 2024, 04:13:13 pm »
Btw, const parameters aren’t even necessarily good. From a low-level perspective, it’s harder to pass a record to a procedure by value, but easier to access from inside the procedure: copied records are accessed as stack pointer register + constant offset, while each of the records passed by reference consumes its own register, leaving fewer registers for useful work (and if they end up spilled, or simply passed through the stack from the beginning because there were too many parameters, they are stored as pointers and accessing their members starts requiring 2 memory accesses each: first read the pointer itself from the stack, then access through the pointer). Maybe for this reason e.g. the recommendation in C++ is to pass string_view (and small PODs in general) by value.

This is an interesting idea but honestly this is sounding more and more like the inline modifier. Florian said before the inline modifier was a mistake and the compiler should doing this for you when it's optimal to do so. Maybe const is smart enough to not pass by pointer if the size is small enough?

The most I think about const the less I'm liking it, mainly because it seems to mix two concepts together, 1) passing optimization for records/value types, 2) minimal read-only protection for classes (if you pass a class by const the caller knows there data MAY be safe).

Feels to me like my default const idea is bad and ideally the compiler should be smart enough to know if the param is not written to and if so possibly pass by pointer if it was more efficient. No need to bother the programmer with this detail.

If you really want a to know a reference type parameter (like a class) will not be changed by passing it to the function then make that parameter "const", and have the compiler be good enough to really ensure this is the case (except for maybe crazy pointer situations, which you should avoid if you're using const).


Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #58 on: April 05, 2024, 04:17:09 pm »
IMHO the Ryan J example from post #43 can be resolved by introducing a const declaration modifier for methods, marking them as accessors (const Self), not mutators in a way that is done in C++ for example.

C++ has a const for methods? As in it knows if the method writes to any fields and requires it to be const?

In Swift if you have a method on a struct and you write to one of its fields you are required to mark the method as "mutating" so the compiler knows calling it may mutate data and thus is not const-compatible. It's kind of annoying and I don't think I'd want to be forced into this for Pascal but it would be useful to opt in to. This would make const vastly more useful than it is now.

Ryan J

  • Full Member
  • ***
  • Posts: 141
Re: Const record parameters by default
« Reply #59 on: April 05, 2024, 04:20:57 pm »
Just curious, what the nature of these functions? Could you post some examples? I'm scanning my code but nothing is jumping out at me.
Code: Pascal  [Select][+][-]
  1. procedure THallControl.DrawSeatGlyph(R: TRect; ACorner: Integer;
  2.   AGlyph: TGraphic);
  3. var
  4.   P: TPoint;
  5. begin
  6.   P := ScaleXY(AGlyph.Width, AGlyph.Height);
  7.   case ACorner of
  8.     1: // Bottom-Left
  9.         R := Rect(R.Left, R.Bottom - P.Y, R.Left + P.X, R.Bottom);
  10.     2: // Bottom-Right
  11.         R := Rect(R.Right - P.X, R.Bottom - P.Y, R.Right, R.Bottom);
  12.     3: // Top-Right
  13.         R := Rect(R.Right - P.X, R.Top, R.Right, R.Top + P.Y);
  14.     4: // Top-Left
  15.         R := Rect(R.Left, R.Top, R.Left + P.X, R.Top + P.Y);
  16.   end;
  17.   Canvas.StretchDraw(R, AGlyph);
  18. end;

Ah yes you're transforming the parameter and reusing it. The idea of overwriting the parameter is questionable but it's more convenient. I would do the same thing myself. Hard to see how bad it would be to have to declare a variable like "translatedR" and if that would make better code. I'd have to try this over a period of time.

 

TinyPortal © 2005-2018