Recent

Author Topic: Immutable data types  (Read 9539 times)

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 348
  • My software never cras....
Re: Immutable data types
« Reply #30 on: September 30, 2022, 10:15:11 pm »
But how can this be done in Free Pascal?

It can't.
But (IMO) it should.
Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

MarkMLl

  • Hero Member
  • *****
  • Posts: 6676
Re: Immutable data types
« Reply #31 on: October 01, 2022, 10:48:31 am »
But how can this be done in Free Pascal?

It can't.
But (IMO) it should.

Hence my example at https://forum.lazarus.freepascal.org/index.php/topic,60749.msg455490.html#msg455490 which could also be extended to e.g. a with extension.

However I'd point out that the core developers have unequivocally rejected this sort of thing in the past: it's their project.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Arioch

  • Sr. Member
  • ****
  • Posts: 421
Re: Immutable data types
« Reply #32 on: October 01, 2022, 08:11:04 pm »
i think it now would be done in the course of Delphi catch-up and it would be like

Code: Pascal  [Select][+][-]
  1. begin
  2.   a := ...
  3.   b := ...
  4.   begin
  5.       const c = a+b;
  6.       ...
  7.  

However, this is only hald a feature, as functional languages do not require immediate binding,

Code: Pascal  [Select][+][-]
  1. val
  2.   b: integer;
  3. begin
  4.     .....
  5.   a := b + 2; // error, b non-init
  6.     ....
  7.   b := 10;   // ok
  8.   a := b + 2; // ok
  9.      ....
  10.   b := 20;  // error, b is already bound
  11.     .....
  12.  

But yeah, probably won't happen.

P.S. as for taking pointers - i do not see it outside the already esiting realm between taking pointers to typed consts, untyped consts and function const parameters. Language should protect us from _accidentally_ doing self-harm, when we intentionally go unsafe we would alway find ways.

Bogen85

  • Hero Member
  • *****
  • Posts: 595
Re: Immutable data types
« Reply #33 on: October 01, 2022, 11:49:54 pm »
The reason why I say that anonymous functions solves this problem, is because my original source code musings from this initial post can use the following (now supported in FPC) to achive immutable variable assignments that the compiler enforces (but yes, as others have stated, could be bypassed with pointers.)

And it does not always need to be anonymous functions. It can be named nested functions. But the point is that const var parameters satisfy cursory compiled enforced immutability and by leveraging them one can cut down the required number of var declarations overall.

MarkMLl

  • Hero Member
  • *****
  • Posts: 6676
Re: Immutable data types
« Reply #34 on: October 02, 2022, 09:07:38 am »
P.S. as for taking pointers - i do not see it outside the already esiting realm between taking pointers to typed consts, untyped consts and function const parameters. Language should protect us from _accidentally_ doing self-harm, when we intentionally go unsafe we would alway find ways.

Which is one reason why I'm a fan of the Modula-3 approach: insist that certain types of operation should only be possible in modules marked as unsafe, and don't allow inexperienced team members to work on unsafe modules.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Immutable data types
« Reply #35 on: October 02, 2022, 11:23:27 am »
P.S. as for taking pointers - i do not see it outside the already esiting realm between taking pointers to typed consts, untyped consts and function const parameters. Language should protect us from _accidentally_ doing self-harm, when we intentionally go unsafe we would alway find ways.
Well, the thing is that you need to use pointers in pascal. Most simple case is classes, class instances are always pointers, there is no way you can use a class without a pointer. Other cases are unknown parameter passing (e.g. when calling QueueAsyncCall you need to pass your data as raw pointer), or when using classical TList, etc.

Using pointers is essential to pascal, and having an immutability concept that does not involve pointers is basically useless, because it is not applicable to a lot of your code. Thats also not always intentional. If you are in a situation where you need to take the pointer, then after you took the pointer and you pass it somewhere all immutability information is lost. So unless you check always where each pointer comes from you can easiely accidentally change the value behind that pointer simply because you don't know that it is supposed to be immutable

MarkMLl

  • Hero Member
  • *****
  • Posts: 6676
Re: Immutable data types
« Reply #36 on: October 02, 2022, 11:33:17 am »
Using pointers is essential to pascal, and having an immutability concept that does not involve pointers is basically useless, because it is not applicable to a lot of your code. Thats also not always intentional. If you are in a situation where you need to take the pointer, then after you took the pointer and you pass it somewhere all immutability information is lost. So unless you check always where each pointer comes from you can easiely accidentally change the value behind that pointer simply because you don't know that it is supposed to be immutable

But you don't need to use pointers or take addresses explicitly in most cases: they've been hidden in well-tested units by the RTL/FCL/LCL developers.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Arioch

  • Sr. Member
  • ****
  • Posts: 421
Re: Immutable data types
« Reply #37 on: October 02, 2022, 05:11:02 pm »
Well, the thing is that you need to use pointers in pascal. Most simple case is classes

When classes are said to be "most simple case" it says a lot :-)

Quote from: Warfley
Using pointers is essential to pascal, and having an immutability concept that does not involve pointers is basically useless,

You seem lacking C++ distinction between int const * V and int * const V
And i agree, on some occasions i lack it too.

But does it mean that const should be removed from Pascal, because it is not hearmetically sealed?

What cost would it take to implement "perfectly const" objects in Pascal? Those that only consists of "pure functions" and are disallowed to have public writeable members, friend members and all direct and indirect ways to change their inner state.

Basically, for people/problems when this rigor is essential - there are other languages already.

Quote from: Warfley
because it is not applicable to a lot of your code.

I think differently.

A simple problem of a function has similar computations on slightly different data.
Like multi-D array.  Data [a1,a2,a3...a8]

I scan the array over a1 dimension, to select some "optimal" position. Maybe minimum, maybe maximum, maybe close to average, or anything.

Then i copy-paste it to a2, a3, a4... - by a same or different criteria.

Then i try to replace those variables, to avoid stupid typo errors, that - after hidden in the noise of boilkerplate - produce what is akin to data [a1, a2, a3, a4, a2, a6, a7, a8]
You know, those lame and stupid
Code: Pascal  [Select][+][-]
  1.   // here is also some noise about IsNan(x), IsInfinite(x) and other special cases.
  2.   if CurrentValue^ < Min3 then
  3.      Min5 := CurrentValue^;  
  4.   if CurrentValue^ > Max5 then
  5.      Min5 := CurrentValue^;  
  6.  

How could values instead of variables help me? well, simply. Pascal would catch my typoes during compilation. I would no more doing loops over

Code: Pascal  [Select][+][-]
  1. var
  2.   CurrentValue: PDouble;
  3.   CurrentMin, CurrentMax: Double;
  4. val
  5.   Min1, Min2, Min3, ...: Double;
  6.   Max1, Max2, Max3, ...: Double;
  7. begin
  8.  
  9. ....
  10.   CurrentMin := data [a1,a2,a3, Low(Data[a1,a2,a3], Low(Data[a1,a2,a3,1], ... );
  11.   CurrentMax := CurrentMin;
  12.   // just any real cell would do, actually. Or i would have to assign to NaN and make a special IsNan case later
  13.  
  14.   for i4 := Low(Data[a1,a2,a3] to HighData[a1,a2,a3] do
  15.     for i5 := Low(Data[a1,a2,a3, i4] to HighData[a1,a2,a3, i4] do
  16.        ....
  17.          begin
  18.            CurrentValue := @data[ a1,a2,a3, i4, i5, ... i8 ];
  19.            // some aux computations might be added there, usually they actually are.
  20.            CurrentMin := Math.Min(CurrentValue^, CurrentMin);
  21.            CurrentMax := Math.Max(CurrentValue^, CurrentMax);
  22.          end;    
  23.  
  24.   Min4 := CurrentMin;  
  25.   Max4 := CurrentMax;  
  26.  

Now, the amount of copy-paste code that has to be edited after copy-paste became several time less.

Most of the code, if it can not be extracted to a inner funciton, found be copy-pasted almost verbatim.

The most noisy and similarly looking things like Max2/Max5 - are no more a problem.
The language would make sure i did assignment and that i only did one single assignment.

Today a cognitive burden is snowballing. I have in evey single line of code remember, which of MaxN and MinN i still should change and which i should never change again. And if i make mistake - spotting it would be very hard and consequences would be hardly traceable, burried deep into computation dark waters.

With values this burden is magically taken off of me. I just know there is no such kind a mistake again. FPC would stop me if i do. I can focus on more essential problems now.

You said the FPC could became regorous and prohibit me taking pointers to Min3 ? Okay, i did not want to do it. Fine.

Code: Pascal  [Select][+][-]
  1. var p: pointer;
  2. begin
  3.    p :=  @'123456';
  4.    p :=  @'123456'[10];
  5.  

Can i take those poiinters? Maybe i can, maybe i can not, in normal code it is irrelevant, i won't try.
If some place requires "hackerish" code - i can go all the way into jungles including external assembler. But those places are few.

So, if FPC would let me take pointers to values, like it lets me taking pointer to const arguments, well, okay, I know pointers are dangerous and unsafe. Everyone does.
When i take pointers - i know i stepped into calamity zone and thrown off FPC's safety net. But often i can do without them.

It is not about "making unsafe code" impossible. It is about *separation* of safe and unsafe code.

Pointers are always poitners;
Code: Pascal  [Select][+][-]
  1. function  Ptr(const s: string): PChar;
  2. var a: string; b: char; c: PChar;
  3. begin
  4.    c := @s[10];
  5.    a := s;
  6.    c^ := #255;
  7.    
  8.    b := a[10];
  9.    c := @b;
  10.    Result := c;
  11. end;
  12.  
  13. ...
  14. var p: PChar;
  15. begin
  16.   p := Ptr('12345');
  17.  
  18.   //// many lines of code with procedure calls
  19.  
  20.   writeln(p^);
  21. end.
  22.  

Would you try to guess what would be written?
Would you tell "const" is useless, defunct, treacherous keyword that should be removed from Pascal?

I do not want for Pascal to become a jail where doing harm and erros  is absoilutely impossible in no way.
I want Pascal to provide me features to sepparate safe and unsafe coding patterns, and to let me code with "least unsafety principle" (to borrow buzz from "least security principle" of OS design)
« Last Edit: October 02, 2022, 05:18:51 pm by Arioch »

Arioch

  • Sr. Member
  • ****
  • Posts: 421
Re: Immutable data types
« Reply #38 on: October 02, 2022, 06:05:08 pm »
But you don't need to use pointers or take addresses explicitly in most cases: they've been hidden in well-tested units by the RTL/FCL/LCL developers.

Or so it should had been.

But Delphi was always pressed by commercial schedule, so Delphi 2009 was released instead of testing.

And even LTS Delphi XE2 can crash her heap manager just because of using "const string" parameters.
Just optimizations (avoiging multi-CPU bus global-lock for ARC) leaked into abstractions.
And i did not even start on runtime backages, that FPC is blessed with having not, for now :-)

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 348
  • My software never cras....
Re: Immutable data types
« Reply #39 on: October 03, 2022, 12:05:30 am »
So, if FPC would let me take pointers to values, like it lets me taking pointer to const arguments, well, okay, I know pointers are dangerous and unsafe. Everyone does.
When i take pointers - i know i stepped into calamity zone and thrown off FPC's safety net. But often i can do without them.

Normally I 'tsk-tsk' at such a statement.  Pointers are a fundamental aspect of Pascal.  Bedrock.

Yet, I found a rarely (once / month) occurring bug literally minutes ago that is pointer related.  Actually related to me "re-purposing" a thread message that came into a thread as a pointer to a data block, but re-purposed to send a simple longword to a dependent/waiting thread.  Of course I used it to pass the message before disposing of the prior data block ... erg.  Embarrassing - but that bug "popped" so rarely that it was hard to trace.  In re-naming the message handles I stumbled on the bug.

So, set up a separate pointer to 'take' the pointer out of the TMessage and "segregate" them...

Pretty lame - (even for me.)  :-[
« Last Edit: October 03, 2022, 12:11:15 am by AlanTheBeast »
Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Immutable data types
« Reply #40 on: October 03, 2022, 01:44:34 am »
This is why you should get equated with the Interlock..... functions and TcriticalSection types.
The only true wisdom is knowing you know nothing

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Immutable data types
« Reply #41 on: October 03, 2022, 03:41:09 am »
When classes are said to be "most simple case" it says a lot :-)
Yes if we are talking about the usage of pointers, classes are the "most simple case" in the sense that it is the most common place. When you create a Lazarus Form application you are bombarded with instances of Buttons, Edits, Forms, etc. So most Lazarus projects use classes everywhere.

But does it mean that const should be removed from Pascal, because it is not hearmetically sealed?
Of course not, this would be really stupid, and I never claimed anything like that. All I did was point out how the immutability concept as introduced in the first post in this thread would not work. And there is a reason for that. As already mentioned above, most FreePascal and Delphi programs are going to be full of classes everywhere. So classes, which basically are nothing but pointers in disguise, are used everywhere. So if an immutability mechanism where to be introduced, if it does not extend to classes (and pointers more generally), it is basically useless, because large parts of most codebases are going to be not covered by this.

This also makes const as it is now in Pascal quite useless as a const mechanism. Take the following example:
Code: Pascal  [Select][+][-]
  1. type
  2.   TIntegerHelper = type helper for Integer
  3.   public
  4.     procedure Inc;
  5.   end;
  6.  
  7. procedure TIntegerHelper.Inc;
  8. begin
  9.   Self += 1;
  10. end;
  11.  
  12. procedure Foo(const i: Integer);
  13. begin
  14.   i.Inc;
  15.   WriteLn(i);
  16. end;
  17.  
  18. begin
  19.   Foo(1);
  20. end.
Result: 2. I just modified a parameter that is "const", just because const does not extend to pointers (the self pointer in this case). Const is a security mechanism, that allows the programmer to create access boundaries to their variables. But, a security mechanism that is not enforcable is useless. This is the programming equivalence from having a key for your front door, while having another key just hanging from the door knob in case you forgot yours.
And the important thing is, here I did it intentionally, but it is easy to see a situation where one person is writing a type (or typehelper) and changes the definition of one method to now alter one of the fields, while the usage, where it is assumed to be constant, is made by a different person. So when the compiler doesn't say: "Hey you are using a method that modifies state on a const object", neither of the developers will probably notice, until they have a very weird bug that they have to spend days in figuring out.

That said, const is not harmfull, it actually has a usecase in that, it allows for optimizations on managed types (like disabling lazy copy checks on strings) when used for parameters. But to enforce immutability of the data it is just plain useless. So no, I don't think it should be removed, I'm just saying that it is pretty useless for enforcing immutability.

Btw, the example above might be categorized as a bug, as you are not supposed to be able to take the pointer of a const, but there is "constref", which basically is a pointer to a constant value, which has the same problem but is more explicelty about it.

What cost would it take to implement "perfectly const" objects in Pascal? Those that only consists of "pure functions" and are disallowed to have public writeable members, friend members and all direct and indirect ways to change their inner state.
No, when I was talking about immutability in this context, I am talking about the immutability of the value of certain data, or you could say to a certain kind of memory (even though this is not necessarily true, as there is not a 1:1 relationship between datatypes in a high level language and memory, just think of register reuse). Basically what the C standard calls an "object" (this term is loaded in Pascal so I try to avoid it).

So no, I don't talk about stateless/pure functions, but that if you have an immutable object, that you cant mutate it. A language that nailed this is actually C++. Consider the following:
Code: C  [Select][+][-]
  1. int j;
  2. struct Test {
  3.   int i;
  4.   void inc() { ++i: }
  5.   void incj() const { ++j; }
  6. };
  7. Test t1 = {1};
  8. Test const t2 = {1};
  9. t1.inc(); // Works fine because t1 is mutable
  10. t2.incj(); // Also works because while t2 is immutable, incj is also immutable
  11. t2.inc(); // error, trying to call non immutable method on immutable data
  12.  
As you can see, incj is not a pure/stateless function, it clearly changes the state of j which is a global variable. But because it is declared as const, the "this" pointer is of type "Test const *", which means it can be called on any Test const object, while inc() changes the state of the "this" pointer and therefore can only be called on a mutable instant of Test.

This has nothing to do with pure functions or anything similar. It is simply that the immutability information is transitively passed through the pointers to this data, in this case the "this" pointer.
If you pass a value as "const", e.g. to another function as parameter, or in any other way, you are saying: "Hey you can read it, but don't touch it, I expect that after you are done this is the exact same value in this data". If you can't trust in this promise, what is it worth?

Quote
I do not want for Pascal to become a jail where doing harm and erros  is absoilutely impossible in no way.
I don't see any merit in allowing things that are clearly errors in any and all cases. For example something like:
Code: Pascal  [Select][+][-]
  1. '1234'[15] := 3.14;
Can never be correct (on multiple levels), and this should never be allowed by the compiler, period. But you are right there are instances in which you want to break the rules, but here I think the important doctrine is, if you do something special, the action to do so should also be special.
Take C++ again, here if you want to access a const object with write, you can, you just need to cast the const away:
Code: C  [Select][+][-]
  1. char const *immutable = str; // Assuming str is mutable so this is not broken
  2. char *mutable = const_cast<char *>(immutable );
By having to take this extra step, you have the programmer explicitly deciding to say "Yes I know I do something dangerous and I am aware of the potential problems".

In my last programming Job we had a rule, whenever a const_cast was in the code, there needed to be an explicit comment explaining exactly why this is necessary. Also the code reviewer would personally ask the programmer about the const_cast and tries to find out if this is really necessary. Only if the code reviewer was satisfied with the answer he would merge the branch into master. If he wasn't satisfied with your answer, you better get working around this.
If you do something special, you better have good reasons to do so

Note that I don't think that C++ does this perfectly, personally I think that immutability by default and mutability annotations (so everything is const unless otherwise specified), is a much better approach. But at least considering what C++ tried to archive, the const mechanism is actually very well thought out and quite useful
« Last Edit: October 03, 2022, 03:46:08 am by Warfley »

MarkMLl

  • Hero Member
  • *****
  • Posts: 6676
Re: Immutable data types
« Reply #42 on: October 03, 2022, 08:22:08 am »
So if an immutability mechanism where to be introduced, if it does not extend to classes (and pointers more generally), it is basically useless, because large parts of most codebases are going to be not covered by this.

But there's a distinction to be made here, which I believe was touched on a couple of weeks ago when somebody was discussing unit-level properties.

* An instance of a class representing e.g. a form is, of course, writable.

* The variable that contains that instance, which is of course actually a pointer, should be assigned once (at program startup) and treated as immutable until shutdown.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 348
  • My software never cras....
Re: Immutable data types
« Reply #43 on: October 03, 2022, 01:53:51 pm »
This is why you should get equated with the Interlock..... functions and TcriticalSection types.

If that was addressed to me, the simpler "should" is don't re-purpose TMessages where in one case the .Msg part is cast as a pointer and in the other case cast as a variable.
Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

Arioch

  • Sr. Member
  • ****
  • Posts: 421
Re: Immutable data types
« Reply #44 on: October 03, 2022, 09:19:08 pm »
And the important thing is, here I did it intentionally, but it is easy to see a situation where one person is writing a type (or typehelper) and changes the definition of one method to now alter one of the fields, while the usage, where it is assumed to be constant, is made by a different person.

Here i have to agree.

However i believe that XE3+ record helpers was abomination anyway. They failed to design proper changes to the compiler and to the type system, so they started to apply what were "ugly hacks for retroactively modifying with closed-source software" to the very foundaiton of RTL.

Whatever is dependent upon simpe type helpers is dependent upon a feature broken by design. You just made one more good case to this.

That said, const is not harmfull, it actually has a usecase in that, it allows for optimizations on managed types

It is. This very optimizatino causes double-free on Delphi XE2 just on conventional long string funciton calls  :-)

Actually there was a long saga between XE2 and XE6 about codegeneration for long string expressons. It was... killing one optimization after another and making simpl Pascal statements produce huge sheets of asm code.

But to enforce immutability of the data it is just plain useless.
So no, I don't think it should be removed, I'm just saying that it is pretty useless for enforcing immutability.

I think i gave you a case.

once-mutable variables to me woul be exactly as much protecting adnd exactly as much leaky as const is.

if you agree that const is - with all the holes - a useful tool to protect you from yourself, then so would be values.

Quote from: Warfley
you are saying: "Hey you can read it, but don't touch it, I expect that after you are done this is the exact same value in this data"

I wonder if you would then try to take pointer to the this->i
Okay, naive attempt would probaly fail, as you would need a "pointer to const" to take i's address (hopefully, don't want to go godbolt). But add a bit of union{...}

All in all, this more fine-grained const distinction in C++ is really nice to had, but retroactively introducing it made their reverse-directin type declaration yet worse...
I can only repeat, there are caseswith D/FPC when i miss it, but those cases are rare.
« Last Edit: October 03, 2022, 09:25:26 pm by Arioch »

 

TinyPortal © 2005-2018