Lazarus

Programming => General => Topic started by: lainz on January 02, 2021, 11:17:21 pm

Title: Nullable and Immutable types
Post by: lainz on January 02, 2021, 11:17:21 pm
Hi, several programming languages supports one time assignable constants (say to store the result of a function call) and as well nullable types.

In the wiki is described how to create a nullable type.
https://wiki.freepascal.org/Nullable_types

How this example can be extended to support one time assignments?

The assignment can be prevented at compile time in some way, or it will work only at runtime?
Title: Re: Nullable and Immutable types
Post by: Peter H on January 03, 2021, 12:00:13 am
So far I see here: https://wiki.freepascal.org/How_to_use_nullable_types (https://wiki.freepascal.org/How_to_use_nullable_types) the nullable types must be explicitely programmatically cleared before first usage.
This is because there is no constructor called automatically in pascal.
So you could add code to the nullable type that assignment is only possible if clear.
(Im unsure how to prevent multiple clearing and reassignement)
I think this must happen at runtime because the compiler can impossibly know at compile time if an assignment is the first assignment, if a program has multiple conditional paths.

Title: Re: Nullable and Immutable types
Post by: lainz on January 03, 2021, 12:03:18 am
I see only at runtime... ok, better than nothing.

Maybe adding another boolean, but again that will be cleared with the clear option that's needed to initialize it...  ::)
Title: Re: Nullable and Immutable types
Post by: Peter H on January 03, 2021, 12:39:08 am
Just an idea, I have not done it, but I think it is possible:
This could be a solution: https://wiki.freepascal.org/FPC_New_Features_3.2.0#Management_operators_for_record_types (https://wiki.freepascal.org/FPC_New_Features_3.2.0#Management_operators_for_record_types)

Using this, the "Clear" Flag can be initialized at time of variable definition.
Programmatical clearing is not necessary then and if the Clear flag is private and Clear procedure deleted,  impossible.
Title: Re: Nullable and Immutable types
Post by: Kays on January 03, 2021, 12:50:21 am
[…] https://wiki.freepascal.org/How_to_use_nullable_types (https://wiki.freepascal.org/How_to_use_nullable_types) the nullable types must be explicitely programmatically cleared before first usage. […]
Well, de facto the FHasValue of the nullable types (https://wiki.freepascal.org/Nullable_types) is zeroed, so virtually “initialized” with false, thus it isn’t actually required, but nevertheless clean programming.

[…] This is because there is no constructor called automatically in pascal. […]
Also, that page was written over 2 years ago. Meanwhile, FPC 3.2.0 has been released, so (not using classes) one could use management operators (https://wiki.freepascal.org/FPC_New_Features_3.2.0#Management_operators_for_record_types) for extended records now. This will eliminate the requirement to “manually” invoke some initialization routine when a variable enters scope.

I see only at runtime... ok, better than nothing. […]
Well, it’s just in trunk for now, but you can probably build something with generics with constants parameters (https://wiki.freepascal.org/Special:PermaLink/142029#Support_for_constant_parameters_in_generics).
Title: Re: Nullable and Immutable types
Post by: Peter H on January 03, 2021, 12:59:53 am
Quote
Well, de facto the FHasValue of the nullable types is zeroed, so virtually “initialized” with false, thus it isn’t actually required, but nevertheless clean programming.

I do not see where "FHasvalue" is zeroed in this example. If the Nullable record is instantiated as local variable, then it occupies random uninitialized memory and there must be some code to initialize it.

I do not know, if global static memory is zeroed, but this might be different on different OS, or the debugger or linker or loader could do it or not do it, so I would not rely on this.
Title: Re: Nullable and Immutable types
Post by: jamie on January 03, 2021, 01:25:28 am
FPC already does some of this stuff in a way that I guess you could call it a auto initialize

Var
   MyVariable :ABasicaType = 0;

begin
 your code..

This of course will zero the variable each time the function is entered .


Then you have the Advanced records which I think was already mentioned here which comes with initialize and finalize constructor options but may only be supported in 3.3 or could be 3.2
 
 But in any case this should allow for managed code to do what ever is needed upon entry to the function.

am I off track here  ?
Title: Re: Nullable and Immutable types
Post by: Warfley on January 03, 2021, 01:35:27 am
I created my own Optional type some time ago and used management operators to initialize it (Mode Delphi):

Code: Pascal  [Select][+][-]
  1. type
  2.   TEmptyOptional = record;
  3.  
  4.   { TOptional }
  5.  
  6.   TOptional<T> = record
  7.   public type
  8.     PT = ^T;
  9.   private
  10.     FHasValue: Boolean;
  11.     FValue: T;
  12.  
  13.     class operator Initialize(var a: TOptional<T>);
  14.   public
  15.     constructor Create(constref AValue: T);
  16.  
  17.     function ValuePtr: PT; inline;
  18.     function Value: T; inline;
  19.     function GetOrDefault(constref def: T): T;
  20.  
  21.     class operator Implicit(constref AValue: T): TOptional<T>; inline;
  22.     class operator Explicit(constref AValue: T): TOptional<T>; inline;
  23.     class operator Implicit(constref AValue: TEmptyOptional): TOptional<T>; inline;
  24.     class operator Explicit(constref AValue: TEmptyOptional): TOptional<T>; inline;
  25.     class operator Implicit(constref opt: TOptional<T>): Boolean; inline;
  26.     class operator Explicit(constref opt: TOptional<T>): Boolean; inline;
  27.     class operator LogicalNot(constref opt: TOptional<T>): Boolean; inline;
  28.  
  29.     property HasValue: Boolean read FHasValue;
  30.  
  31.     class function Empty: TOptional<T>; static; inline;
  32.   end;
  33.  
  34. function EmptyOptional: TEmptyOptional; inline;
  35.  
  36. implementation
  37.  
  38. { TOptional }
  39.  
  40. class operator TOptional<T>.Initialize(var a: TOptional<T>);
  41. begin
  42.   a.FHasValue := False;
  43. end;
  44.  
  45. constructor TOptional<T>.Create(constref AValue: T);
  46. begin
  47.   FHasValue := True;
  48.   FValue := AValue;
  49. end;
  50.  
  51. function TOptional<T>.ValuePtr: PT;
  52. begin
  53.   If not HasValue then
  54.     raise ENoValuePresentException.Create('Trying to access value of an empty optional');
  55.   Result := @FValue;
  56. end;
  57.  
  58. function TOptional<T>.Value: T;
  59. begin
  60.   If not HasValue then
  61.     raise ENoValuePresentException.Create('Trying to access value of an empty optional');
  62.   Result := FValue;
  63. end;
  64.  
  65. function TOptional<T>.GetOrDefault(constref def: T): T;
  66. begin
  67.   If HasValue then
  68.     Result := FValue
  69.   else
  70.     Result := def;
  71. end;
  72.  
  73. class operator TOptional<T>.Implicit(constref AValue: T): TOptional<T>;
  74. begin
  75.   Result.FHasValue := True;
  76.   Result.FValue := AValue;
  77. end;
  78.  
  79. class operator TOptional<T>.Explicit(constref AValue: T): TOptional<T>;
  80. begin
  81.   Result.FHasValue := True;
  82.   Result.FValue := AValue;
  83. end;
  84.  
  85. class operator TOptional<T>.Implicit(constref AValue: TEmptyOptional): TOptional
  86.   <T>;
  87. begin
  88.   Result.FHasValue := False;
  89. end;
  90.  
  91. class operator TOptional<T>.Explicit(constref AValue: TEmptyOptional): TOptional
  92.   <T>;
  93. begin
  94.   Result.FHasValue := False;
  95. end;
  96.  
  97. class operator TOptional<T>.Implicit(constref opt: TOptional<T>): Boolean;
  98. begin
  99.   Result := opt.HasValue;
  100. end;
  101.  
  102. class operator TOptional<T>.Explicit(constref opt: TOptional<T>): Boolean;
  103. begin
  104.   Result := opt.HasValue;
  105. end;
  106.  
  107. class operator TOptional<T>.LogicalNot(constref opt: TOptional<T>): Boolean;
  108. begin
  109.   Result := not opt.HasValue;
  110. end;
  111.  
  112. class function TOptional<T>.Empty: TOptional<T>;
  113. begin
  114.   Result.FHasValue := False;
  115. end;
  116.  
  117. function EmptyOptional: TEmptyOptional;
  118. begin
  119.   // Nothing to do here
  120. end;
  121.  

The main difference to the TNullable example (except for the management initialization) is that it does not take pointers to be nullable, as this requires a runtime check, but can be cleared by using a custom dummy type TEmptyOptional, which is generated via the function emptyOptional, i.e.:
Code: Pascal  [Select][+][-]
  1. myOpt := EmptyOptional;
It also has some more casts that make it's usage easier (like implicit cast to bool to check for it's existance)

If you want to make that type single assignment readonly you can do the following:
1. remove the ValuePtr function, as this allows writing to the value without our access.
2. Implement the copy class operator as follows:
Code: Pascal  [Select][+][-]
  1. class operator TOptional<T>.Copy(constref aSrc: TOptional<T>;
  2.                                       var aDst: TOptional<T>);
  3. begin
  4.   if aDst then
  5.     raise EAlreadyAssignedQuestion.Create('Trying to override already assigned optional');
  6.   aDst.FHasValue := aSrc.FHasValue;
  7.   if aSrc then
  8.     aDst.FValue := aSrc.FValue;
  9. end;

Note: you also need to define EAlreadyAssignedQuestion as exception type:
Code: Pascal  [Select][+][-]
  1. EAlreadyAssignedQuestion = class(Exception);

But this is only a runtime check and is not enforced on compiletime
Title: Re: Nullable and Immutable types
Post by: Peter H on January 03, 2021, 01:49:28 am
@Jamie
I tried this, gives a compile error:
Code: Pascal  [Select][+][-]
  1. Type testtype = record
  2.    a:integer;
  3.    end;
  4.  
  5. procedure TForm1.btnDisconnectClick(Sender: TObject);
  6. var
  7.   a:testtype = 0;
  8. .....
  9.                  

Is a modeswitch required?
(I use {$mode objfpc}{$H+} FPC 3.3.1)
Title: Re: Nullable and Immutable types
Post by: Warfley on January 03, 2021, 02:03:29 am
@Jamie
I tried this, gives a compile error:
I think the syntax is this:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTestRec = record
  3.     A, B: Integer;
  4.   end;
  5.  
  6. var
  7.   test: TTestRec = (A: 42; B: 32);
Title: Re: Nullable and Immutable types
Post by: Peter H on January 03, 2021, 02:11:48 am
The question was how to zero out a variable or record. Initializing is possible, but maybe painful for large records, especially if the record is generated by a template.

Here is a brute force solution that works on any FPC version:
Code: Pascal  [Select][+][-]
  1. Type testtype = record
  2.    a:integer;
  3.    end;
  4.  
  5. procedure TForm1.btnDisconnectClick(Sender: TObject);
  6. var
  7.   a:testtype;
  8. begin
  9.   FillChar(a,sizeof(a),0);
  10. ......
  11.  
:D
Title: Re: Nullable and Immutable types
Post by: Warfley on January 03, 2021, 02:22:31 am
If you write the record yourself and you have FPC 3.2 or above, I would recommend just implementing the initialization operator, so when using this record you don't have to think about it, this can also be done using fillchar:
Code: Pascal  [Select][+][-]
  1. class operator TMyType.Initialize(var a: TMyType);
  2. begin
  3.   FillChar(self, sizeof(self), #00);
  4. end;

It should be added that if the record contains managed types that don’t default to 0, fillchar will break the data integrity
Title: Re: Nullable and Immutable types
Post by: jamie on January 03, 2021, 03:53:46 am
People keep forgetting about the Object model..

that can do just about anything the advanced record can do..

Infact I would bet the compiler just uses bits and pieces of it

just change the Record to a OBJECT..

put in a simple INIT procedure method...



and when in use just call that at the start of the operation and when the method is called you can code there all that you need for the default stuff.

also Objects do not need to be created dynamic, they can live on the stack or heap and they behave pretty much the same way as records do.

EDIT:
 Apparently the RECORD also supports standard methods too. oh well
so what's the real difference here ? Maybe objects don't have the operators I guess.

Title: Re: Nullable and Immutable types
Post by: Thaddy on January 03, 2021, 05:52:08 am
If you write the record yourself and you have FPC 3.2 or above, I would recommend just implementing the initialization operator, so when using this record you don't have to think about it, this can also be done using fillchar:
Code: Pascal  [Select][+][-]
  1. class operator TMyType.Initialize(var a: TMyType);
  2. begin
  3.   FillChar(self, sizeof(self), #00);
  4. end;

It should be added that if the record contains managed types that don’t default to 0, fillchar will break the data integrity
Well, I would do:
Code: Pascal  [Select][+][-]
  1. class operator TMyType.Initialize(var a: TMyType);
  2. begin
  3.   a := Default(TMyType);
  4. end;
For the reason you mention and that's where the default() intrinsic is for.
Title: Re: Nullable and Immutable types
Post by: PascalDragon on January 03, 2021, 12:47:01 pm
Just for information: FPC has an TNullable<> in unit Nullable (https://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/packages/rtl-objpas/src/inc/nullable.pp?view=markup) since August as well. It will also be part of 3.2.2.
Title: Re: Nullable and Immutable types
Post by: lainz on January 03, 2021, 04:06:34 pm
Thanks!

So only missing the immutable, but I think if is only at runtime it has no sense, it should be displayed at compile time if I try to assign it.
Title: Re: Nullable and Immutable types
Post by: Bogen85 on September 29, 2022, 03:03:56 am
Thanks!
So only missing the immutable, but I think if is only at runtime it has no sense, it should be displayed at compile time if I try to assign it.

Yes... I would really like to have this to...
Only at runtime creates a lot of potential problematic corner cases that you don't find out soon enough...
TinyPortal © 2005-2018