Primitive Record Wrappers

classic Classic list List threaded Threaded
39 messages Options
12
Reply | Threaded
Open this post in threaded view
|

Primitive Record Wrappers

Mazo Winst
Hello list,

In some situations
, would be very interesting to determine whether the value of a primitive variable is initialized or not. For example, fields in database records can contain null values. By implementing an ORM, it would be interesting to represent these null values in the properties of objects.

Java has the so called Primitives Class Wrappers, where each primitive type (int, boolean ...)
has a corresponding wrapper class (Integer, Boolean ...). By using class wrappers you can represent null values in primitive types through null class instances references.

Using class wrappers in FPC is not feasible due to memory management issues (the programmer needs to worry about the lifetime of the instances and free the instances
manually).

Perhaps, a solution in FPC would be using primitive record wrappers with custom records operators.

What do you think about it? Is there any other solution?

Regards

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

leledumbo
Administrator
> What do you think about it? Is there any other solution?

There's a reason why http://www.freepascal.org/docs-html/3.0.0/fcl/db/tfield.isnull.html is invented
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
2016-02-18 8:00 GMT-02:00 leledumbo <[hidden email]>:
> What do you think about it? Is there any other solution?

There's a reason why
http://www.freepascal.org/docs-html/3.0.0/fcl/db/tfield.isnull.html is
invented


You didnt understand my point. Design classes coupled with DB framework is not a solution.

In the following class


type
   TPerson = class(TObject)
   private
     
FId: Integer;
   
  FName: String;    
      FBirthDate: TDateTime;    

   public
      property Id: Integer read FId write FId;
      property Name: String read FName write FName;
      property BirthDate: TDateTime read FBirthDate write FBirthDate;
   end;


Suppose that this class represent data of the Person table in a sql database. Suppose that there is records where the column "BirthDate" is null. How do i represent a null "BirthDate" in my object?

Regards


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Marco van de Voort
In our previous episode, Mazola Winstrol said:
>    end;
>
>
> Suppose that this class represent data of the Person table in a sql
> database. Suppose that there is records where the column "BirthDate" is
> null. How do i represent a null "BirthDate" in my object?

Keep using variants instead of typed data.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
In reply to this post by Mazo Winst
Em 18/02/2016 10:12, "Marco van de Voort" <[hidden email]> escreveu:

>
> In our previous episode, Mazola Winstrol said:
> >    end;
> >
> >
> > Suppose that this class represent data of the Person table in a sql
> > database. Suppose that there is records where the column "BirthDate" is
> > null. How do i represent a null "BirthDate" in my object?
>
> Keep using variants instead of typed data.
>

I think this is not a good alternative. As the variant are untyped, should the consumers of the person class assume that BirthDate are filled as a formated date string '01/01/1980' or as a native tdatetime?

I think it would be better using record wrappers to primitive types. See http://blogs.embarcadero.com/abauer/2008/09/18/38869.

What do you think about that?

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Michalis Kamburelis-3
2016-02-18 15:43 GMT+01:00 Mazola Winstrol <[hidden email]>:

> Em 18/02/2016 10:12, "Marco van de Voort" <[hidden email]> escreveu:
>>
>> In our previous episode, Mazola Winstrol said:
>> >    end;
>> >
>> >
>> > Suppose that this class represent data of the Person table in a sql
>> > database. Suppose that there is records where the column "BirthDate" is
>> > null. How do i represent a null "BirthDate" in my object?
>>
>> Keep using variants instead of typed data.
>>
>
> I think this is not a good alternative. As the variant are untyped, should
> the consumers of the person class assume that BirthDate are filled as a
> formated date string '01/01/1980' or as a native tdatetime?
>
> I think it would be better using record wrappers to primitive types. See
> http://blogs.embarcadero.com/abauer/2008/09/18/38869.
>
> What do you think about that?

Personally, I like the Nullable<T> idea from C#, which is basically a
generic record adding a "HasValue: boolean" to "Value: T". The
type-safety is nice, and it's sometimes very useful (when you cannot
"cheat" and say that "empty string" or "zero value" always means
"uninitialized"). Looking at your link
http://blogs.embarcadero.com/abauer/2008/09/18/38869 , this is exactly
trying to implement Nullable<T> in Object Pascal.

The major problem there is that it's difficult to force it to be
always initialized with zeroes. Currently, a non-global variable of
unmanaged type can always be filled with memory garbage, as explained
in other thread. The trick in
http://blogs.embarcadero.com/abauer/2008/09/18/38869 to use interfaces
feels ugly, it's an over-use of the "interface" specifics in Object
Pascal.

I remember a thread on fpc-devel "Pascal Smart Pointers Idea + ARC
implementation" where the original author (Maciej Izak) was already
working to overcome this:
https://www.mail-archive.com/fpc-devel@.../msg33172.html
. His reasons was similar (force the contents to be initialized with
zero) and his solution felt cleaner -- introduce "class operator
Initialize" where you can initialize your record.

Any chance that this would be added/merged to FPC one day?:)

Regards,
Michalis
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Sven Barth-2

Am 20.02.2016 06:25 schrieb "Michalis Kamburelis" <[hidden email]>:
> I remember a thread on fpc-devel "Pascal Smart Pointers Idea + ARC
> implementation" where the original author (Maciej Izak) was already
> working to overcome this:
> https://www.mail-archive.com/fpc-devel@.../msg33172.html
> . His reasons was similar (force the contents to be initialized with
> zero) and his solution felt cleaner -- introduce "class operator
> Initialize" where you can initialize your record.
>
> Any chance that this would be added/merged to FPC one day?:)

I'm indeed inclined to implement this one day, though I don't know yet how exactly I want it to look.

Regards,
Sven


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
In reply to this post by Michalis Kamburelis-3
2016-02-20 3:24 GMT-02:00 Michalis Kamburelis <[hidden email]>:
The major problem there is that it's difficult to force it to be
always initialized with zeroes. Currently, a non-global variable of
unmanaged type can always be filled with memory garbage, as explained
in other thread. The trick in
http://blogs.embarcadero.com/abauer/2008/09/18/38869 to use interfaces
feels ugly, it's an over-use of the "interface" specifics in Object
Pascal.


A less "ugly" code could be to store the pointer value in an array of bytes (TBytes). As the TBytes type is a managed type, it will always be initialized.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Sven Barth-2

Am 21.02.2016 03:02 schrieb "Mazola Winstrol" <[hidden email]>:
>
> 2016-02-20 3:24 GMT-02:00 Michalis Kamburelis <[hidden email]>:
>>
>> The major problem there is that it's difficult to force it to be
>> always initialized with zeroes. Currently, a non-global variable of
>> unmanaged type can always be filled with memory garbage, as explained
>> in other thread. The trick in
>> http://blogs.embarcadero.com/abauer/2008/09/18/38869 to use interfaces
>> feels ugly, it's an over-use of the "interface" specifics in Object
>> Pascal.
>>
>
> A less "ugly" code could be to store the pointer value in an array of bytes (TBytes). As the TBytes type is a managed type, it will always be initialized.

The managed type isn't needed for the data (that should be stored as a plain field to let the compiler take care of finalization), but instead it's needed to store whether the field contains a valid value (not Null) or not (null). For this potentially any managed type (array, string, interface) can be used, but it might indeed be that the manual interface approach is the fastest, because there one can avoid unnecessary locking (it's done by the implementations of AddRef and Release) which is not the case with arrays and strings as the reference counting routines always do locking.

Regards,
Sven


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Maciej Izak
In reply to this post by Sven Barth-2
2016-02-20 9:23 GMT+01:00 Sven Barth <[hidden email]>:

Am 20.02.2016 06:25 schrieb "Michalis Kamburelis" <[hidden email]>:
> I remember a thread on fpc-devel "Pascal Smart Pointers Idea + ARC
> implementation" where the original author (Maciej Izak) was already
> working to overcome this:
> https://www.mail-archive.com/fpc-devel@.../msg33172.html
> . His reasons was similar (force the contents to be initialized with
> zero) and his solution felt cleaner -- introduce "class operator
> Initialize" where you can initialize your record.
>
> Any chance that this would be added/merged to FPC one day?:)

I'm indeed inclined to implement this one day, though I don't know yet how exactly I want it to look.

Finally I have some time to continue my work on this. I need to take break from Sparta/Lazarus :P. Hopefully all will be available soon on smart_pointers branch.

--
Best regards,
Maciej Izak

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
Hello,

In the code bellow, the generic type TNullableTyple is implemented (and incomplete for now).

Is there any possibility of "nullable types" be added to RTL or anyother fpc provided package?


unit NullableTypes;

{$mode delphi}{$H+}

interface 

type

  { TNullable }

  TNullable<T> = record
  strict private
    FValue: T;
    FHasValue: IInterface;
    function GetValue: T;
    function GetHasValue: Boolean;
    procedure SetValue(const AValue: T);
    procedure SetFlatInterface(var Intf: IInterface);
  public
    constructor Create(const AValue: T);
    function GetValueOrDefault: T; overload;
    function GetValueOrDefault(Default: T): T; overload;
    property HasValue: Boolean read GetHasValue;
    property Value: T read GetValue;

    class operator Implicit(AValue: TNullable<T>): T;
    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Explicit(AValue: TNullable<T>): T;
  end;

  TInteger = TNullable<Integer>;


function NopAddref(inst: Pointer): Integer; stdcall;
function NopRelease(inst: Pointer): Integer; stdcall;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult;

const
  FlagInterfaceVTable: array[0..2] of Pointer =
  (
    @NopQueryInterface,
    @NopAddref,
    @NopRelease
  );
  FlagInterfaceInstance: Pointer = @FlagInterfaceVTable;

implementation

uses
  SysUtils;



function NopAddref(inst: Pointer): Integer; stdcall;
begin
  Result := -1;
end;

function NopRelease(inst: Pointer): Integer; stdcall;
begin
  Result := -1;
end;

function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult;
stdcall;
begin
  Result := E_NOINTERFACE;
end;

{ TNullable }

procedure TNullable<T>.SetFlatInterface(var Intf: IInterface);
begin
  Intf := IInterface(@FlagInterfaceInstance);
end;

class operator TNullable<T>.Explicit(AValue: TNullable<T>): T;
begin
  Result := AValue.Value;
end;

function TNullable<T>.GetHasValue: Boolean;
begin
  Result := FHasValue <> nil;
end;

function TNullable<T>.GetValue: T;
begin
  if not HasValue then
    raise Exception.Create('Invalid operation, Nullable type has no value');
  Result := FValue;
end;

function TNullable<T>.GetValueOrDefault: T;
begin
  if HasValue then
    Result := FValue
  else
   Result := Default(T);
end;

function TNullable<T>.GetValueOrDefault(Default: T): T;
begin
  if not HasValue then
    Result := Default
  else
    Result := FValue;
end;

class operator TNullable<T>.Implicit(AValue: TNullable<T>): T;
begin
  Result := AValue.Value;
end;

class operator TNullable<T>.Implicit(const AValue: T): TNullable<T>;
begin
  Result := TNullable<T>.Create(AValue);
end;

procedure TNullable<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
  SetFlatInterface(FHasValue);
end;

constructor TNullable<T>.Create(const AValue: T);
begin
  FValue := AValue;
  SetFlatInterface(FHasValue);
end;        

end.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

dmitry boyarintsev
On Fri, Feb 26, 2016 at 10:38 PM, Mazola Winstrol <[hidden email]> wrote:
In the code bellow, the generic type TNullableTyple is implemented (and incomplete for now).
How to reset TNullableType to Null value? HasValue seems to be read-only.  
 
Is there any possibility of "nullable types" be added to RTL or anyother fpc provided package? 

You could start your own package separate from RTL.
It's not about where the package is hosted, it's about letting others know that the package exists.

thanks,
Dmitry 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Maciej Izak
In reply to this post by Mazo Winst
2016-02-27 4:38 GMT+01:00 Mazola Winstrol <[hidden email]>:
In the code bellow, the generic type TNullableTyple is implemented (and incomplete for now).


It can be done more optimal with incoming operators Initialize and Finalize. I have working implementation but I need to adjust the code to the FPC compiler code standard :P


--
Best regards,
Maciej Izak

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Sven Barth-2

Am 27.02.2016 09:36 schrieb "Maciej Izak" <[hidden email]>:
>
> 2016-02-27 4:38 GMT+01:00 Mazola Winstrol <[hidden email]>:
>>
>> In the code bellow, the generic type TNullableTyple is implemented (and incomplete for now).
>>
>
> It can be done more optimal with incoming operators Initialize and Finalize. I have working implementation but I need to adjust the code to the FPC compiler code standard :P

Sidenote for this (don't know whether you have already ensured that): these operators must not be useable as global operators as after all they must he known in the RTTI and that can't work if they're declared in another unit that might not even be included... (That's in contrast to how operators normally behave in FPC, but there's not really a way around it :/ )

Regards,
Sven


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
I've implemented a new version. For this version i created a mock class to use with FHasValue (the previous implementation uses a hack to the interface internal layout).


unit NullableTypes;

{$mode delphi}{$H+}

interface

type

  { TMockInterfacedObject }

  TMockInterfacedObject = class(TObject, IUnknown)
  strict private
    class var FInstance: TMockInterfacedObject;
  public
    class constructor Create;
    class destructor Destroy;
    class property Instance: TMockInterfacedObject read FInstance;
    function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    function _AddRef: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
    function _Release: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  end;

  { TNullable }

  TNullable<T> = record
  strict private
    FValue: T;
    FHasValue: IInterface;
    function GetValue: T;
    function GetHasValue: Boolean;
    procedure SetValue(const AValue: T);
    procedure SetFlatInterface(var Intf: IInterface);
  public
    constructor Create(const AValue: T);
    procedure Clear;
    function GetValueOrDefault: T; overload;
    function GetValueOrDefault(Default: T): T; overload;
    property HasValue: Boolean read GetHasValue;
    property Value: T read GetValue;

    class operator Implicit(const AValue: Pointer): TNullable<T>;
    class operator Implicit(AValue: TNullable<T>): T;
    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Explicit(AValue: TNullable<T>): T;
  end;


  TInteger = TNullable<Integer>;

implementation

uses
  SysUtils;

{ TNullable }

procedure TNullable<T>.SetFlatInterface(var Intf: IInterface);
begin
  Intf := TMockInterfacedObject.Instance;
end;

class operator TNullable<T>.Explicit(AValue: TNullable<T>): T;
begin
  Result := AValue.Value;
end;

function TNullable<T>.GetHasValue: Boolean;
begin
  Result := FHasValue <> nil;
end;

function TNullable<T>.GetValue: T;
begin
  if not HasValue then
    raise Exception.Create('Invalid operation, Nullable type has no value');
  Result := FValue;
end;

function TNullable<T>.GetValueOrDefault: T;
begin
  if HasValue then
    Result := FValue
  else
   Result := Default(T);
end;

function TNullable<T>.GetValueOrDefault(Default: T): T;
begin
  if not HasValue then
    Result := Default
  else
    Result := FValue;
end;

class operator TNullable<T>.Implicit(const AValue: Pointer): TNullable<T>;
begin
  if AValue = nil then
    Result.Clear
  else
    raise Exception.Create('Invalid operation, incompatible values.');
end;

class operator TNullable<T>.Implicit(AValue: TNullable<T>): T;
begin
  Result := AValue.Value;
end;

class operator TNullable<T>.Implicit(const AValue: T): TNullable<T>;
begin
  Result := TNullable<T>.Create(AValue);
end;

procedure TNullable<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
  SetFlatInterface(FHasValue);
end;

constructor TNullable<T>.Create(const AValue: T);
begin
  FValue := AValue;
  SetFlatInterface(FHasValue);
end;

procedure TNullable<T>.Clear;
begin
  FHasValue := nil;
end;

{ TMockInterfacedObject }

class constructor TMockInterfacedObject.Create;
begin
  FInstance := TMockInterfacedObject.Create;
end;

class destructor TMockInterfacedObject.Destroy;
begin
  FInstance.Free;
end;

function TMockInterfacedObject.QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref
  {$ELSE}const{$ENDIF} iid : tguid;out obj): longint;
begin
  Result := E_NOINTERFACE;
end;

function TMockInterfacedObject._AddRef: longint;
begin
  Result := -1;
end;

function TMockInterfacedObject._Release: longint;
begin
  Result := -1;
end;

end.


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
In reply to this post by dmitry boyarintsev
2016-02-27 0:49 GMT-03:00 Dmitry Boyarintsev <[hidden email]>:
On Fri, Feb 26, 2016 at 10:38 PM, Mazola Winstrol <[hidden email]> wrote:
In the code bellow, the generic type TNullableTyple is implemented (and incomplete for now).
How to reset TNullableType to Null value? HasValue seems to be read-only.  

In the last version, there is two ways:

var
   NullInt: TNullable<Integer>;
begin
   NullInt.Clear; // Method 1
   NullInt := nil; // Method 2
end;



_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

silvioprog
In reply to this post by Mazo Winst
On Sat, Feb 27, 2016 at 2:49 PM, Mazola Winstrol <[hidden email]> wrote:
I've implemented a new version. For this version i created a mock class to use with FHasValue (the previous implementation uses a hack to the interface internal layout).

Thanks for share that. It seems a very useful unit.

unit NullableTypes;

{$mode delphi}{$H+}

$MODE DELPHI implies {$H ON}, so so you should remove the {$H+}.

--
Silvio Clécio

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Maciej Izak
In reply to this post by Sven Barth-2
2016-02-27 9:44 GMT+01:00 Sven Barth <[hidden email]>:
Sidenote for this (don't know whether you have already ensured that): these operators must not be useable as global operators as after all they must he known in the RTTI and that can't work if they're declared in another unit that might not even be included... (That's in contrast to how operators normally behave in FPC, but there's not really a way around it :/ )

Yes that is true :). Maybe in next week (or in 2 weeks) my implementation will be ready to commit. Current implementation on my machine, works fine even for code with

-New
-GetMem + Initialize/InitializeArray

it is important base for SmartPtr / SmartObj.
--
Best regards,
Maciej Izak

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
In reply to this post by silvioprog
2016-02-27 14:58 GMT-03:00 silvioprog <[hidden email]>:

$MODE DELPHI implies {$H ON}, so so you should remove the {$H+}.


Thanks for the tip! i will remove for the next version.

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Primitive Record Wrappers

Mazo Winst
I have noticed that, by using generics, we can't overload arithmetic operators.

E.g.:

...
class operator Negative(A: TNullableType<T>): T;
...

class operator TNullableType<T>.Negative(A: TNullableType<T>): T;
begin
    Result := -1 * A;
end;
....

wouldn't compile with the error "Operator not applicable to this operand type".

Is there any workaround to overcome this?



_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
12