Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2016-07

"Default" discussion for SmartPointers etc

43 messages · 7 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Maciej Izak

Hi,

Finally I have a working implementation (not published yet) of Smart Pointers/ARC objects and Nullable (Nilable?) types. I think is worth to discuss a little about new "default modifier" (which is strictly related to mentioned structures). If needed I can correct details.

As you know (or not) all is based on new modifier - "default" for fields. Overriding operators like "." or "^" is complicated and not obvious in Pascal world.

The "default" modifier is inspired by "default" modifier for indexed properties. All of my work is (I hope so) natural for Pascal language. All FPC tests pass fine (phew!). The idea is quiet simple:

"If all fails - try to use default field".

Example implementation of SmartPointers and SmartObjects available at:

https://github.com/maciej-izak/PascalSmartPointers (see OUTPUT on the end of each of file)

Tests (very good way to see how it works. NOTE: I need to add few other tests for functions with var/const/out, "for in do" loop and for arrays and indexed properties - help with additional tests is welcome):

https://github.com/maciej-izak/PascalSmartPointers/tree/master/tests

The way how to obtain pointer can be a little confusing for most of Pascal programmers. Anyway nothing new for Pascal language. In Pascal we have little known @@ operator to get pointer to variable which handle pointer to procedure/function. For records with "default field" @ means "get pointer to default field" and @@ means "get pointer to record".

Any questions and suggestions are welcome!

Side note: as side effect of new feature we can implement very compatible ARC objects compatible with Delphi without breaking existing code base and without ugly additions for TObject like:

    function __ObjAddRef: Integer; virtual;
    function __ObjRelease: Integer; virtual;
    property RefCount: Integer read FRefCount;
    procedure DisposeOf; // that can become also part of TObject

All can be delegated into record (record with default field works like proxy, note: each of method/field/property/operator of record has higher priority than "default field object"). We can omit easily TComponent problem for ARC compiler like mentioned here (maybe we can add some hidden field for TObject similar to hfMonitorOffset from Delphi to point proxy record for destroy notification - maybe as part of TMonitor):

https://plus.google.com/+DalijaPrasnikar/posts/3CUnrZam6zp (see comments)

more info about DisposeOf:

https://plus.google.com/+HoracioJoseCavalcantiFilho/posts/Nhp7wbHX2hM (see comments)

Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak
Maciej Izak wrote 33 lines
Hi, Finally I have a working implementation (not published yet) of Smart Pointers/ARC objects and Nullable (Nilable?) types. I think is worth to discuss a little about new "default modifier" (which is strictly related to mentioned structures). If needed I can correct details. As you know (or not) all is based on new modifier - "default" for fields. Overriding operators like "." or "^" is complicated and not obvious in Pascal world. The "default" modifier is inspired by "default" modifier for indexed properties. All of my work is (I hope so) natural for Pascal language. All FPC tests pass fine (phew!). The idea is quiet simple: "If all fails - try to use default field". Example implementation of SmartPointers and SmartObjects available at: https://github.com/maciej-izak/PascalSmartPointers (see OUTPUT on the end of each of file) Tests (very good way to see how it works. NOTE: I need to add few other tests for functions with var/const/out, "for in do" loop and for arrays and indexed properties - help with additional tests is welcome): https://github.com/maciej-izak/PascalSmartPointers/tree/master/tests The way how to obtain pointer can be a little confusing for most of Pascal programmers. Anyway nothing new for Pascal language. In Pascal we have little known @@ operator to get pointer to variable which handle pointer to procedure/function. For records with "default field" @ means "get pointer to default field" and @@ means "get pointer to record".

Very nice job indeed.

I can see a lot of potential in this for ORM implementations, where IS NULL is always a difficult concept.

As for ARC: I think this is a difficult topic, which should better be discussed in a separate thread.

Michael.

Reply by email

Sven Barth
to Maciej Izak: Finally I have a working implementation (not published yet) of Smart Pointers/ARC objects…
Maciej Izak wrote 7 lines
Hi, Finally I have a working implementation (not published yet) of Smart Pointers/ARC objects and Nullable (Nilable?) types. I think is worth to discuss a little about new "default modifier" (which is strictly related to mentioned structures). If needed I can correct details.

First of let me tell you that in essence and in general I'm in support of your "default" idea (and the resulting idea to use this for smart pointers), though I have a few points, but I hope that we'll be able to solve these. :)

As you know (or not) all is based on new modifier - "default" for fields. Overriding operators like "." or "^" is complicated and not obvious in Pascal world.

Agreed.

Show quoted text 10 lines
The "default" modifier is inspired by "default" modifier for indexed properties. All of my work is (I hope so) natural for Pascal language. All FPC tests pass fine (phew!). The idea is quiet simple: "If all fails - try to use default field". Example implementation of SmartPointers and SmartObjects available at: https://github.com/maciej-izak/PascalSmartPointers (see OUTPUT on the end of each of file)

First of it might be better not to name the tests "tdefaultX.pp" as that is already used for the Default() intrinsic... :/

Second I don't know whether it's a good idea to use this with fields (though kudos for omitting the ";" between type and "default" ;) ). Maybe it would be better - to stay with the source of your idea - to only allow this for properties. This would allow to keep the field itself private for example and control its access through the setter (and ordinary as well as management operators), something that one couldn't do if one would need to use operators as the field would need to be public to be really useful outside of the record instance.

Third I don't really agree with the notion that the record methods, fields, etc. take precedence to the default field. See further down for my suggestion to solve this (though that idea isn't without its flaws either).

Question: can "default" only be used in "record" or also in "object" and "class"?

Show quoted text 6 lines
Tests (very good way to see how it works. NOTE: I need to add few other tests for functions with var/const/out, "for in do" loop and for arrays and indexed properties - help with additional tests is welcome): https://github.com/maciej-izak/PascalSmartPointers/tree/master/tests

Note: tests for visibility.

Show quoted text 6 lines
The way how to obtain pointer can be a little confusing for most of Pascal programmers. Anyway nothing new for Pascal language. In Pascal we have little known @@ operator to get pointer to variable which handle pointer to procedure/function. For records with "default field" @ means "get pointer to default field" and @@ means "get pointer to record".

While I have to admit that I haven't known the @@-operator I don't necessarily agree with its use. Take tdefault7 and tdefault8 for example where you let the left hand side of the assignment determine which pointer is used. In tdefault7 it's the pointer to "a" while in tdefault8 it's the pointer to "a.DefaultValue". Since normally in Pascal the result type of an expression is *not* determined by the left hand side of an assignment that's rather confusing (yes, there are exceptions, but that doesn't mean that one needs to add a new one).

My idea to solve this coincides with my idea to solve the problem to access the record instead of the default field: typecasts.

Take tdefault11 for example (which suffers from the same problem I mentioned above):

=== code begin ===

program tdefault11;
{$MODE DELPHI}
type
  TFoo<T> = record
    Field1: Integer;
    DefaultValue: T default;
    Field2: Integer;
  end;
  TFooInt = TFoo<Integer>;
  TFooFooInt = TFoo<TFooInt>;
var
  a: TFooFooInt;
  pi: PInteger;
  pfi: ^TFooInt;
  pffi: ^TFooFooInt;
begin
  a := 123;
  pi := @a; // accesses a.DefaultValue.DefaultValue
  pfi := @TFooInt(a); // forces access to a.DefaultValue
  pffi := @TFooFooInt(a); // forces access to a
  if Pointer(pi) = Pointer(pfi) then
    Halt(1);
  if Pointer(pi) = Pointer(pffi) then
    Halt(2);
  if Pointer(pfi) = Pointer(pffi) then
    Halt(3);
  if pi^  123 then
    Halt(4);
  if pfi^  123 then
    Halt(5);
  if pffi^  123 then
    Halt(6);
end.

=== code end ===

Essentially a typecast would disable the default field for the type it had been casted to.

Of course this idea with the default field taking precedence becomes tainted a bit if one considers the management operators or the assignment operators as these would be part of the record and not the default field, but would still need to work...

Note: don't forget to test with global operator overloads ;)

Regards,
Sven

Reply by email

Maciej Izak
to Sven Barth

2016-07-26 21:59 GMT+02:00 Sven Barth :

Show quoted text 5 lines
First of let me tell you that in essence and in general I'm in support of your "default" idea (and the resulting idea to use this for smart pointers), though I have a few points, but I hope that we'll be able to solve these. :)

Note: Generally I am working on boost-spirit like solution for Pascal so one single bad move will destroy my further plans...

First of it might be better not to name the tests "tdefaultX.pp" as that is already used for the Default() intrinsic... :/

No problem with that - we can change this ;)

Show quoted text 9 lines
Second I don't know whether it's a good idea to use this with fields (though kudos for omitting the ";" between type and "default" ;) ). Maybe it would be better - to stay with the source of your idea - to only allow this for properties. This would allow to keep the field itself private for example and control its access through the setter (and ordinary as well as management operators), something that one couldn't do if one would need to use operators as the field would need to be public to be really useful outside of the record instance.

Property was considered for "default" as main option but has many weakness. Sure, default "non indexed" property can be added as addition but definitely not as main idea, because:

1. It breaks tdefault17 :
https://github.com/maciej-izak/PascalSmartPointers/blob/master/tests/tdefault17.pp http://forum.lazarus.freepascal.org/index.php/topic,32482.msg209584.html#msg209584

(yes I know we can use property for this but you need to declare additional getter/setter which is not much elegant in that case)

2. "default" for field is unique for [Nil|Null]able types (fastest possible implementation, and very correct for Pascal language. Nilable type works exactly like pointers with nice additions):

=== code begin ===
type
  // record constraint is designed for nullable types :P see #24073
  // #24073 Is already fixed in my implementation. record constraint accepts
  // all not nilable types (including sets and strings - empty string as
logical entity is not nil at all)
  TNullable<T: record> = record
  public type
    PT = ^T;
  strict private
    Instance: ^T default;
    function GetValue: T;
  public
    property Value: T read GetValue;
    function HasValue: Boolean; inline;
    function GetValueOrDefault: T;
    class operator Implicit(A: T): TNullable<T>;
    class operator Implicit(A: TNullable<T>): T;
    class operator Implicit(A: PT): TNullable<T>;
    class operator Equal(A: TNullable<T>; B: PT): Boolean;
    class operator Initialize(var A: TNullable<T>);
    class operator Finalize(var A: TNullable<T>);
    class operator Clone(constref Src: TNullable<T>; var Dest:
TNullable<T>);
    class operator Copy(var Rec: TNullable<T>);
  end;
=== code end ===

3. "default" need to be transparent and usable with existing code base, some compiler magic is part of my further compiler development (I mean here "Storage Modifiers" and ARC objects in DelphiNextgen mode). With current approach is possible to pass record with default field as var/out/constref parameter. With property that is impossible.

4. for property for records there exist well know problems for "with"

5. "default" is designed as specialized helpers for TArray<T> and for Interfaces, property will block passing "default field" as var/out/constref parameter (similar to point 3).

Show quoted text 8 lines
Third I don't really agree with the notion that the record methods, fields, etc. take precedence to the default field. See further down for my suggestion to solve this (though that idea isn't without its flaws either). Question: can "default" only be used in "record" or also in "object" and "class"?

Current implementation allows "default" only for "records".

Show quoted text 10 lines
> > Tests (very good way to see how it works. NOTE: I need to add few other > tests for functions with var/const/out, "for in do" loop and for arrays > and indexed properties - help with additional tests is welcome): > > https://github.com/maciej-izak/PascalSmartPointers/tree/master/tests Note: tests for visibility.

Right, but note that "default" is implicitly used even when is declared as strict private (which is correct and by design - for example see TNullable above). For "default" non indexed properties that rule might be incorrect.

Show quoted text 11 lines
> The way how to obtain pointer can be a little confusing for most of > Pascal programmers. Anyway nothing new for Pascal language. In Pascal we > have little known @@ operator to get pointer to variable which handle > pointer to procedure/function. For records with "default field" @ means > "get pointer to default field" and @@ means "get pointer to record". While I have to admit that I haven't known the @@-operator I don't necessarily agree with its use. Take tdefault7 and tdefault8 for example where you let the left hand side of the assignment determine which pointer is used. In tdefault7 it's the pointer to "a" while in tdefault8 it's the pointer to "a.DefaultValue".

@@ operator is very simple way to determine where you point. In any other case we have casting hell. See below (and more below). @@ exist rather as fulfillment to pa := @a; form tdefault7.pp (anyway is necessary for untyped pointers). You might not like @@ operator but @@ is part of long tradition. In practice works like a charm and is very clear.

Show quoted text 5 lines
Since normally in Pascal the result type of an expression is *not* determined by the left hand side of an assignment that's rather confusing (yes, there are exceptions, but that doesn't mean that one needs to add a new one).

That is also by design. That is because you can't declare as "default field" nor "normal field" the field of owner type, so as logical consequence the syntax needs to be allowed (I mean here pa := @a; form tdefault7.pp). In daily usage it works very well and any other compiler behavior is irrational. Thank this feature most of programmers don't need to use @@ syntax (btw. the idea of introducing this was not forcing others to use @@). Additionally is necessary to get ride of untyped/typed pointers so we have rules presented in example tdefault8. Anyway for complex solution @@ is needed.

Show quoted text 7 lines
My idea to solve this coincides with my idea to solve the problem to access the record instead of the default field: typecasts. ... Essentially a typecast would disable the default field for the type it had been casted to.

That was my first implementation (!!!). Which is definitely bad. :\ It breaks a lot of other important rules. For example is impossible to get ride of Implicit/Explicit operators when as "default field" is declared record which has Implicit/Explicit operators. Really, really bad idea, end-user code looks terrible and is unclear.

Show quoted text 5 lines
Of course this idea with the default field taking precedence becomes tainted a bit if one considers the management operators or the assignment operators as these would be part of the record and not the default field, but would still need to work...

Remember that "default" is designed not "only" for SmartPtr/Obj and for Nilable types but also for "specialized" helpers for any type (for example for TArray<T>, for Interfaces etc). We can't do exception only for management operators and for assignment operators - unclear rule.

Note: don't forget to test with global operator overloads ;)

I will try to remember ;)

Best regards, Maciej Izak

Reply by email

Maciej Izak
to Maciej Izak

2016-07-27 10:12 GMT+02:00 Maciej Izak :

TNullable<T: record> = record

curiosity: Pascal nilable types are more elastic than C# nullable types:

Nested "nilable" types are allowed. :)

Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak: 2016-07-26 21:59 GMT+02:00 Sven Barth : Note: Generally I am working on boost-spirit like…
Maciej Izak wrote 6 lines
3. "default" need to be transparent and usable with existing code base, some compiler magic is part of my further compiler development (I mean here "Storage Modifiers" and ARC objects in DelphiNextgen mode). With current approach is possible to pass record with default field as var/out/constref parameter. With property that is impossible.

This is probably the only good argument.

Show quoted text 6 lines
> > Question: can "default" only be used in "record" or also in "object" and > "class"? > Current implementation allows "default" only for "records".

I don't think that for classes this should be allowed.

Imagine:

TSomeClass = Class
   Property SomeProp : TSomeClass; default;
end;
Var
   A,B : TSomeClass;
begin
   A:=B;  // What to do ? Set A, or A.SomeProp ?
end;

For records, this construct is simply not possible.

Show quoted text 5 lines
@@ operator is very simple way to determine where you point. In any other case we have casting hell. See below (and more below). @@ exist rather as fulfillment to pa := @a; form tdefault7.pp (anyway is necessary for untyped pointers). You might not like @@ operator but @@ is part of long tradition. In practice works like a charm and is very clear.

I agree and think @@ is indeed better.

Show quoted text 13 lines
> Since normally in Pascal the > result type of an expression is *not* determined by the left hand side > of an assignment that's rather confusing (yes, there are exceptions, but > that doesn't mean that one needs to add a new one). > That is also by design. That is because you can't declare as "default field" nor "normal field" the field of owner type, so as logical consequence the syntax needs to be allowed (I mean here pa := @a; form tdefault7.pp). In daily usage it works very well and any other compiler behavior is irrational.

? A compiler always behaves rational.

Michael.

Reply by email

Jonas Maebe
to Maciej Izak: 2016-07-26 21:59 GMT+02:00 Sven Barth : Note: Generally I am working on boost-spirit like…

Maciej Izak wrote on Wed, 27 Jul 2016:

Show quoted text 5 lines
@@ operator is very simple way to determine where you point. In any other case we have casting hell. See below (and more below). @@ exist rather as fulfillment to pa := @a; form tdefault7.pp (anyway is necessary for untyped pointers). You might not like @@ operator but @@ is part of long tradition. In practice works like a charm and is very clear.

Getting rid of the @@-operator is also part of a long tradition in FPC. In fact, it was one of the first differences between FPC and Turbo Pascal modes, and it is the reason why we use a different procvar syntax (because we considered it very unclear). There is no @@-operator in FPC/ObjFPC modes at this time.

On a more general note, I think the fact that @recordvar no longer is a pointer to the record is wrong. You need a different keyword instead of "record" in that case (or possibly a modifier for the "record" keyword, like with "class abstract" -- but this is not a record at any more than an shortstring is a record, so I don't think that's a good idea either).

Completely changing how certain aspects of a record are treated by the compiler only based on the fact that somewhere there is a "default" modifier for one field is not good. You should definitely not have to go through an entire declaration of a record to be able to know how it will behave.

Jonas

Reply by email

Maciej Izak
to Jonas Maebe

2016-07-27 11:24 GMT+02:00 Jonas Maebe :

Show quoted text 12 lines
On a more general note, I think the fact that @recordvar no longer is a pointer to the record is wrong. You need a different keyword instead of "record" in that case (or possibly a modifier for the "record" keyword, like with "class abstract" -- but this is not a record at any more than an shortstring is a record, so I don't think that's a good idea either). Completely changing how certain aspects of a record are treated by the compiler only based on the fact that somewhere there is a "default" modifier for one field is not good. You should definitely not have to go through an entire declaration of a record to be able to know how it will behave.

I was thinking on additional modifier for that before. We can use something like this:

=== code begin ===
type
TNullable<T: record> = record proxy to Instance
public type

    PT = ^T;
  strict private
    Instance: ^T; // no default word anymore
    function GetValue: T;

=== code end ===

Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak
Maciej Izak wrote 27 lines
2016-07-27 11:24 GMT+02:00 Jonas Maebe : > > On a more general note, I think the fact that @recordvar no longer is a > pointer to the record is wrong. You need a different keyword instead of > "record" in that case (or possibly a modifier for the "record" keyword, > like with "class abstract" -- but this is not a record at any more than an > shortstring is a record, so I don't think that's a good idea either). > > Completely changing how certain aspects of a record are treated by the > compiler only based on the fact that somewhere there is a "default" > modifier for one field is not good. You should definitely not have to go > through an entire declaration of a record to be able to know how it will > behave. I was thinking on additional modifier for that before. We can use something like this: === code begin === type TNullable<T: record> = record proxy to Instance public type PT = ^T; strict private Instance: ^T; // no default word anymore function GetValue: T; === code end ===

Record proxy will not work. Proxy can be the name of a field.

So I think the modifier should be before the record, just as 'packed' or 'bitpacked' are.

And contrary to 'to instance', I prefer the default:

type
   TNullable<T: record> = proxy record
   public type
     PT = ^T;
   strict private
     Instance: ^T; default;
     function GetValue: T;
   end;

Michael.

Reply by email

Maciej Izak
to Michael Van Canneyt

2016-07-27 13:11 GMT+02:00 Michael Van Canneyt :

Instance: ^T; default;

";" between default and type will not work.

TNullable<T: record> = proxy record
...

looks good for me, even better than pure record, the context is more clear.

Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak
Maciej Izak wrote 6 lines
2016-07-27 13:11 GMT+02:00 Michael Van Canneyt : > Instance: ^T; default; ";" between default and type will not work.

Sorry, typo :/

Show quoted text 5 lines
TNullable<T: record> = proxy record ... looks good for me, even better than pure record, the context is more clear.

Yes. Exactly what Jonas wanted to achieve, I suppose.

Michael.

Reply by email

Sven Barth
to Maciej Izak: 2016-07-27 13:11 GMT+02:00 Michael Van Canneyt : ";" between default and type will not…
"Maciej Izak" wrote 13 lines
2016-07-27 13:11 GMT+02:00 Michael Van Canneyt : > > Instance: ^T; default; ";" between default and type will not work. TNullable<T: record> = proxy record ... looks good for me, even better than pure record, the context is more

clear.

Indeed. Though like Michael I'd keep the "default" keyword cause otherwise you'd use an identifier that isn't available yet.

I'll comment again on the @@-topic later on (I'm at work right now).

Regards,
Sven

Reply by email

Jonas Maebe
to Michael Van Canneyt: Sorry, typo :/ Yes. Exactly what Jonas wanted to achieve, I suppose. Michael.

Michael Van Canneyt wrote on Wed, 27 Jul 2016:

Show quoted text 9 lines
On Wed, 27 Jul 2016, Maciej Izak wrote: > > TNullable<T: record> = proxy record > ... > > looks good for me, even better than pure record, the context is more clear. Yes. Exactly what Jonas wanted to achieve, I suppose.

A bitpacked or packed record still behaves like a regular record. If does not behave like a record, it should not be called a record.

Additionally, the @@/@ operator thing is inconsistent with how e.g. ansistrings or dynamic arrays work: @ansistringvar does not get you the address of the first character, but of the variable itself. Similarly, there is no @@ansistringvar to get the address of the ansistring variable itself rather than of the first character, even though the 'value' of an ansistring starts at that first character.

Procedure variables in Turbo Pascal (and Delphi) are the only type for which this approach was ever was used, and given that they didn't use it anymore later on may indicate that they also thought it was not a very good idea in hindsight. Let alone that we would also start accepting @@@-expressions
(https://github.com/maciej-izak/PascalSmartPointers/blob/master/tests/tdefault21.pp ).

The fact that you want this operation so the proxy can be transparent in most cases and then be intransparent in some other cases, indicates that there may be something wrong with the way the concept is designed. It's a bit like having a pointer type that you always want to be implicitly dereferenced, so then you add @pointer to get the value of the pointer and @@pointer to get the address of the pointer variable itself.

It would seem better to me that you do have to add something after your proxy object (specify a field, call a method, use proxyobject[x], ...) to get the proxied value. Just like with a class, where "instance" by itself can never refer to the default property (it's always "instance[x]").

The discussion about var-parameters similarly worries me. I think it is a desirable feature that you cannot pass such a proxy object directly as a var-parameter to a routine expecting a plain value. Just like you, indeed, can never pass a property as a var-parameter, even if it maps directly to a field. You should not be able to implicitly strip away the proxy object, since it obviously changes (possibly in a very invasive way) how that value can be manipulated (the value could be strict private and only gettable/settable via methods normally, so it is limited to certain values). It breaks the contract if you can do such direct manipulations anyway.

One possible alternative is to severely restrict such a proxy object at the language level: make sure its definition cannot add any additional functionality beyond what is strictly necessary for read-only proxying (except possibly for implicit conversion operators, which then also must be forbidden from changing the value). In that case, passing the proxied value directly by reference is no problem because after returning, the proxied object will be guaranteed to still be in a valid state (since the proxy object has no ability to restrict what value could be assigned to the proxied object field if it were done via the proxy).

Jonas

Reply by email

Michael Van Canneyt
to Jonas Maebe
Jonas Maebe wrote 16 lines
Michael Van Canneyt wrote on Wed, 27 Jul 2016: > On Wed, 27 Jul 2016, Maciej Izak wrote: > >> >> TNullable<T: record> = proxy record >> ... >> >> looks good for me, even better than pure record, the context is more >> clear. > > Yes. Exactly what Jonas wanted to achieve, I suppose. A bitpacked or packed record still behaves like a regular record. If does not behave like a record, it should not be called a record.

Well, it can be a special kind of type helper.

Symply extend the syntax for type helper with a new word.

Show quoted text 14 lines
Additionally, the @@/@ operator thing is inconsistent with how e.g. ansistrings or dynamic arrays work: @ansistringvar does not get you the address of the first character, but of the variable itself. Similarly, there is no @@ansistringvar to get the address of the ansistring variable itself rather than of the first character, even though the 'value' of an ansistring starts at that first character. Procedure variables in Turbo Pascal (and Delphi) are the only type for which this approach was ever was used, and given that they didn't use it anymore later on may indicate that they also thought it was not a very good idea in hindsight. Let alone that we would also start accepting @@@-expressions (https://github.com/maciej-izak/PascalSmartPointers/blob/master/tests/tdefault21.pp ).

Why not introduce an address operator ?

operator @ (T : MyType) : Pointer;

Michael.

Reply by email

Jonas Maebe
to Michael Van Canneyt

Michael Van Canneyt wrote on Wed, 27 Jul 2016:

Why not introduce an address operator ? operator @ (T : MyType) : Pointer;

It's only needed because Maciej wants the proxy type to behave differently from all other existing types. The solution is not to add functionality to make that easier, but to fix the inconsistencies.

Jonas

Reply by email

Michael Van Canneyt
to Jonas Maebe
Jonas Maebe wrote 10 lines
Michael Van Canneyt wrote on Wed, 27 Jul 2016: > Why not introduce an address operator ? > > operator @ (T : MyType) : Pointer; It's only needed because Maciej wants the proxy type to behave differently from all other existing types. The solution is not to add functionality to make that easier, but to fix the inconsistencies.

Then I didn't understand what exactly you think is inconsistent ?

Knowing the purpose, what do you think can be done ?

Michael.

Reply by email

Jonas Maebe
to Michael Van Canneyt

Michael Van Canneyt wrote on Wed, 27 Jul 2016:

Show quoted text 14 lines
On Wed, 27 Jul 2016, Jonas Maebe wrote: > Michael Van Canneyt wrote on Wed, 27 Jul 2016: > >> Why not introduce an address operator ? >> >> operator @ (T : MyType) : Pointer; > > It's only needed because Maciej wants the proxy type to behave > differently from all other existing types. The solution is not to > add functionality to make that easier, but to fix the > inconsistencies. Then I didn't understand what exactly you think is inconsistent ?

The need for a special behaviour of the "@" operator: to return not the address of the proxy object, but the address of the proxied object. That is different compared to the behaviour of the @-operator on any other type (except for procvars in TP/Delphi-mode, but that is an inconsistency we can't do much about).

Knowing the purpose, what do you think can be done ?

I wrote some ideas in the original email you replied to.

Jonas

Reply by email

Maciej Izak
to Jonas Maebe: Michael Van Canneyt wrote on Wed, 27 Jul 2016: A bitpacked or packed record still behaves…

2016-07-27 15:33 GMT+02:00 Jonas Maebe :

It would seem better to me that you do have to add something after your proxy object (specify a field, call a method, use proxyobject[x], ...) to get the proxied value. Just like with a class, where "instance" by itself can never refer to the default property (it's always "instance[x]").

In that case SmartPtr/SmartObj/Nullable type has no sense for me. The basic purpose is excluded. You can do that today by using for example proxyobject._.foo();

To get rid of @@/@@@ we can use typecast + new type kind. With "proxy" type, Sven proposition has more sense:

=== code begin ===
{$MODE DELPHI}
type
  TRawSomeSmart<T> = record
  private
    Instance: T;
    ... // normal record
  end;
  TSomeSmart<T> = proxy to TRawSomeSmart<T>.Instance;
var
  ptr: pointer;
  // PTypeInfo(TypeInfo(ni)).Kind = tkProxy
  ni: TSomeSmart<Integer>;
  np: TSomeSmart<TProcedure>;
begin
  ptr := @ni; // pointer to ni.Instance
  ptr := @TSomeSmart<Integer>(ni); // pointer to ni
  ptr := @@np; // pointer to np.Instance
  ptr := @np   // pointer to procedure
  ptr := @TSomeSmart<TProcedure>(np); // pointer to np
end;
=== code end ===
Best regards, Maciej Izak

Reply by email

Sven Barth
to Maciej Izak
"Maciej Izak" wrote 5 lines
2016-07-27 15:33 GMT+02:00 Jonas Maebe : > > It would seem better to me that you do have to add something after your

proxy object (specify a field, call a method, use proxyobject[x], ...) to get the proxied value. Just like with a class, where "instance" by itself can never refer to the default property (it's always "instance[x]").

In that case SmartPtr/SmartObj/Nullable type has no sense for me. The
basic purpose is excluded. You can do that today by using for example
proxyobject._.foo();
To get rid of @@/@@@ we can use typecast + new type kind. With "proxy"

type, Sven proposition has more sense:

Show quoted text 27 lines
=== code begin === {$MODE DELPHI} type TRawSomeSmart<T> = record private Instance: T; ... // normal record end; TSomeSmart<T> = proxy to TRawSomeSmart<T>.Instance; var ptr: pointer; // PTypeInfo(TypeInfo(ni)).Kind = tkProxy ni: TSomeSmart<Integer>; np: TSomeSmart<TProcedure>; begin ptr := @ni; // pointer to ni.Instance ptr := @TSomeSmart<Integer>(ni); // pointer to ni ptr := @@np; // pointer to np.Instance ptr := @np // pointer to procedure ptr := @TSomeSmart<TProcedure>(np); // pointer to np end; === code end ===

Hmm, not that bad either. At least then it would be absolutely clear that TSomeSmart is something special and not an ordinary record.

Regards,
Sven

Reply by email

Jonas Maebe
to Sven Barth
Sven Barth wrote 7 lines
Am 27.07.2016 21:04 schrieb "Maciej Izak" < >: > > > In that case SmartPtr/SmartObj/Nullable type has no sense for me. The basic purpose is excluded. You can do that today by using for example proxyobject._.foo();

What is the basic purpose? I did not see any explanation of that in the original mail. It only said that it is vital to the smart pointers/ARC objects and nullable types implementation.

Show quoted text 31 lines
> To get rid of @@/@@@ we can use typecast + new type kind. With "proxy" > type, Sven proposition has more sense: > > === code begin === > {$MODE DELPHI} > > type > TRawSomeSmart<T> = record > private > Instance: T; > ... // normal record > end; > > TSomeSmart<T> = proxy to TRawSomeSmart<T>.Instance; > > var > ptr: pointer; > // PTypeInfo(TypeInfo(ni)).Kind = tkProxy > ni: TSomeSmart<Integer>; > np: TSomeSmart<TProcedure>; > begin > ptr := @ni; // pointer to ni.Instance > ptr := @TSomeSmart<Integer>(ni); // pointer to ni > > ptr := @@np; // pointer to np.Instance > ptr := @np // pointer to procedure > ptr := @TSomeSmart<TProcedure>(np); // pointer to np > end; > === code end === Hmm, not that bad either.

I think it is still bad, because it again makes "@X" not return the address of X, but of something that X refers to (it's almost like an "absolute" declaration). Additionally, declaring a new type (TSomeSmart<T>) that refers to an entity inside another type (TRawSomeSmart<T>.Instance) looks quite strange (there's nothing like that in Pascal yet either, not even to declare forward types).

Before continuing, I think it would be a good idea to look at what the desired concept is exactly (transparent proxy objects/references, thin wrappers that allow to specify some default behaviour, nullable types, ... ?) already exists in other programming language (not C++ -- everything is just meta-programmed using templates there) and how things work there at least from a specification standpoint.

Properly designing a new programming language concept is hard, and the worst thing is that once it's shipped, you are stuck with it basically forever. I think it needs more than just some example programs and tests to design one from scratch.

It's really too bad there is no programming language committee for the Pascal language with language design experts that could discuss and refine this in-depth. I'm no language design expert myself at all and preferably would not even get involved in this kind of discussions but at the same time I don't want to get stuck with having to maintain and support an ever growing language that becomes less and less self-consistent and more and more complex (while there may be simpler or clearer ways to achieve the same things).

Jonas

Reply by email

Martin Schreiber
to Jonas Maebe
Jonas Maebe wrote 15 lines
Properly designing a new programming language concept is hard, and the worst thing is that once it's shipped, you are stuck with it basically forever. I think it needs more than just some example programs and tests to design one from scratch. It's really too bad there is no programming language committee for the Pascal language with language design experts that could discuss and refine this in-depth. I'm no language design expert myself at all and preferably would not even get involved in this kind of discussions but at the same time I don't want to get stuck with having to maintain and support an ever growing language that becomes less and less self-consistent and more and more complex (while there may be simpler or clearer ways to achieve the same things).

I second this. It is really sad what happened with the Delphi language and even worse with Free Pascal and its different dialects in recent years.

Martin

Reply by email

Michael Van Canneyt
to Jonas Maebe: What is the basic purpose? I did not see any explanation of that in the original mail. It…
Jonas Maebe wrote 12 lines
On 27/07/16 22:47, Sven Barth wrote: > Am 27.07.2016 21:04 schrieb "Maciej Izak" < > >: >> >> >> In that case SmartPtr/SmartObj/Nullable type has no sense for me. The > basic purpose is excluded. You can do that today by using for example > proxyobject._.foo(); What is the basic purpose? I did not see any explanation of that in the original mail. It only said that it is vital to the smart pointers/ARC objects and nullable types implementation.

As far as I understand I think Izak wants (needs) 2 things:

- "Default" property for a property of records.
I think that this feature by itself presents little difficulty.

This is enough to implement "nillable" types, except for 1 thing. (second point)

- The ability to pass properties as var arguments.

I think this feature is not doable. The closest you can come to this is:

   tmp:=A.Prop;
   try
     MyProcedureVarArgument(tmp);
   finally
     A.Prop:=Tmp;
   end;
   But this presents obvious timing problems: A.Prop is written only on return of the function.
   Failing that, Izak tries to simulate the 'address' of the default property
   of a record.

IMHO The first can be done, the solution to the second is a bad idea.

Michael.

Reply by email

Sven Barth
to Jonas Maebe: What is the basic purpose? I did not see any explanation of that in the original mail. It…
"Jonas Maebe" wrote
Before continuing, I think it would be a good idea to look at what the

desired concept is exactly (transparent proxy objects/references, thin wrappers that allow to specify some default behaviour, nullable types, ... ?) already exists in other programming language (not C++ -- everything is just meta-programmed using templates there) and how things work there at least from a specification standpoint.

The main features that Maciej wants to achieve - at least as far as I can see it - is ARC without changing TObject (basically the approach that one would choose in C++) and Nullable types.
For the later I've found that C# and also Oxygene implement this, however they don't do this based on the type, but the variable.

=== code begin ===

var
  x: nullable Integer;

=== code end ===

Pointer types (in Oxygene these are only object references, in FPC that would also include the managed strings and dynamic arrays) are inherently nullable ("nullable Pointer" or "nullable TObject" wouldn't be any different from regular "Pointer" and "TObject") this also introducing "non nullable", though I don't think we'd need this for now. I haven't checked in detail, but there is probably a check for Nil plus an exception if a Nil-nullable is assigned to a non-nullable variable (something like "as" basically).

So we /could/ solve the Nullable topic by using the approach Oxygene has chosen, maybe only with an exception of var-parameters (as internally it would probably be represented by a automatically generated container record).

For the reference counting topic there aren't many alternatives than the C++ approach (after all CLR and JVM support GC or have inherent ARC) and while they do use templates for that they also rely on the extensive operator overloading capabilities of C++, something that we don't have in Pascal.

The main need would be an easy way to call the methods of the reference counted instance while hiding the "gory" details.

Something like this would already be possible with only the management operators:

=== code begin ===

procedure Something;
var
  o: TRef<TObject>;
begin
  o := TStringList.Create;
  Writeln(o.Obj.ClassName);
end;

=== code end ===

Now the desire is merely to get rid of the "Obj.".

That's where Maciej's idea for the "default" keyword came from, cause that allows something similar for array properties.

If we implement the Nullable I mentioned above we could decouple the two features and for example restrict "default" to fields to a structured type (or better yet a property to such a type, as "var" support shouldn't be included anyway).

What Maciej's approach would have allowed is for some ingenious developer to fond a creative (ab)use for the feature.

Regards,
Sven

Reply by email

Michael Van Canneyt
to Sven Barth
Sven Barth wrote 34 lines
Am 27.07.2016 23:27 schrieb "Jonas Maebe" : > Before continuing, I think it would be a good idea to look at what the desired concept is exactly (transparent proxy objects/references, thin wrappers that allow to specify some default behaviour, nullable types, ... ?) already exists in other programming language (not C++ -- everything is just meta-programmed using templates there) and how things work there at least from a specification standpoint. The main features that Maciej wants to achieve - at least as far as I can see it - is ARC without changing TObject (basically the approach that one would choose in C++) and Nullable types. For the later I've found that C# and also Oxygene implement this, however they don't do this based on the type, but the variable. === code begin === var x: nullable Integer; === code end === Pointer types (in Oxygene these are only object references, in FPC that would also include the managed strings and dynamic arrays) are inherently nullable ("nullable Pointer" or "nullable TObject" wouldn't be any different from regular "Pointer" and "TObject") this also introducing "non nullable", though I don't think we'd need this for now. I haven't checked in detail, but there is probably a check for Nil plus an exception if a Nil-nullable is assigned to a non-nullable variable (something like "as" basically). So we /could/ solve the Nullable topic by using the approach Oxygene has chosen, maybe only with an exception of var-parameters (as internally it would probably be represented by a automatically generated container record).

I don't see how you can solve nullable var parameters in native code.

There is no way you can detect a write to the address, necessary to set the not-null flag.

So I would conclude that "nullable" support is incompatible with var params.

Assuming that is the case, the "default property in record" approach invented by Izak seems IMHO more powerful than

var
   x: nullable Integer;

If you consider that

  X : TNullable<Integer>;

makes X a different type from 'integer', the fact that you cannot pass it in a var parameter (expecting Integer) simply follows from the strict type checking rule of pascal.

I find that easier to explain than explaining that a non-nullable defined as

  x: nullable Integer;

("which is after all an integer") cannot be used in var parameters.

Michael.

Reply by email

Stefan Glienke
to Michael Van Canneyt

One thing that I did not see mentioned here is the fact that introducing "operator hoisting" by introducing that default feature will not solve nullables at all.

I think a basic rule of nullables (at least in C# where I know them from) is the fact that operators can only be applied to a nullable if the underlying type supports them. But the result of an operation that involves a nullable will always be a nullable (because it can happen than any of the operants is null).

That means that a := b + c where b or/and c are nullable<int> always causes a to be nullable<int>. Just introducing default where the addition will be performed by the underlying value is not enough because a) the result (if both are not null) has to be converted back into a nullable<int> and b) the result needs to be null if either b or c happen to be null. Both cannot be done by just promoting the addition operator. Because if b or c happen to be null an assignment to an integer would raise an error (at least in C# that is the case - see https://msdn.microsoft.com/en-us/library/ydkbatt6(v=vs.110).aspx)

Reply by email

Maciej Izak
to Michael Van Canneyt: I don't see how you can solve nullable var parameters in native code. There is no way you…

2016-07-28 12:40 GMT+02:00 Michael Van Canneyt :

I don't see how you can solve nullable var parameters in native code.

I'd like to present how TNullable<T> works in my implementation (compilable and runable program ;)

=== code begin ===
procedure Test1(var x: Integer);
begin
  x := 1;
end;
procedure Test2(var x: TNullable<Integer>);
begin
  x := 2;
end;
var
  a: TNullable<Integer>;
begin
  try
    WriteLn(a.Value);
  except
    on E: EAccessViolation do
      WriteLn(a.HasValue); // print false
  end;
  a := 0;
  WriteLn(a.HasValue); // print true
  if a.HasValue then
  begin
    WriteLn(a.Value); // print 0
    WriteLn(a^); // print 0
  end;
  Test1(a^);
  WriteLn(a.Value); // print 1
  Test2(a);
  WriteLn(a.Value); // print 2
end.
=== code end ===
Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak
Maciej Izak wrote 7 lines
2016-07-28 12:40 GMT+02:00 Michael Van Canneyt : > I don't see how you can solve nullable var parameters in native code. > I'd like to present how TNullable<T> works in my implementation (compilable and runable program ;)

Yes, I remember you demonstrate it. But I think there are some caveats.

Show quoted text 6 lines
=== code begin === procedure Test1(var x: Integer); begin x := 1; end;

Assume a is null before the call to test1:

   test1(a);

1. What happens if this procedure is empty, i.e. no write is performed ? (or it is performed conditionally)
On return, is a null or not ?

2. Assume your variable is null before the call to Test1. What is the value on entry into Test1 ? i.e. what happens if

    begin
      if X=0 then
        x:=1
      else
        X:=23;
    end

3. How do you detect an actual write ?

4. What happens if Test1 is an external C procedure ?

Michael.

Reply by email

Maciej Izak
to Michael Van Canneyt

2016-07-28 13:30 GMT+02:00 Michael Van Canneyt :

Show quoted text 5 lines
Assume a is null before the call to test1: test1(a);

That won't compile.

Show quoted text 5 lines
1. What happens if this procedure is empty, i.e. no write is performed ? (or it is performed conditionally) On return, is a null or not ?

You are not able to call test1 in presented form (you will get error: Incompatible types).

Show quoted text 10 lines
2. Assume your variable is null before the call to Test1. What is the value on entry into Test1 ? i.e. what happens if begin if X=0 then x:=1 else X:=23; end

That won't compile too. The correct code:

=== code begin ===

  if x.HasValue and (x^ = 0) then
    x := 1
  else
    x := 23;

=== code end ===

or

=== code begin ===
  if x.GetValueOrDefault = 0 then
    x := 1
  else
    x := 23;
=== code end ===

3. How do you detect an actual write ?

4. What happens if Test1 is an external C procedure ?

It depend on context. If we have:

x := 1; // is detected in class operator Implicit(A: T): TNullable<T>;

when you use

x^ := 1; // you are operating directly on Instance field

in that case if x.HasValue is false you will get EAccessViolation

Best regards, Maciej Izak

Reply by email

Maciej Izak
to Stefan Glienke: One thing that I did not see mentioned here is the fact that introducing "operator…

2016-07-28 13:02 GMT+02:00 Stefan Glienke :

Show quoted text 5 lines
One thing that I did not see mentioned here is the fact that introducing "operator hoisting" by introducing that default feature will not solve nullables at all.

In most of cases we can solve that by class operators but I think that for few cases compiler magic is needed. We can use additional compiler support ony for that form of nullable types:

var
  x: nullable Integer; // which is de facto TNullable<T> on steroids ;)
Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak: 2016-07-28 13:30 GMT+02:00 Michael Van Canneyt : That won't compile. You are not able to…
Maciej Izak wrote 18 lines
2016-07-28 13:30 GMT+02:00 Michael Van Canneyt : > > Assume a is null before the call to test1: > > test1(a); > That won't compile. > > 1. What happens if this procedure is empty, i.e. no write is performed ? > (or it is performed conditionally) > On return, is a null or not ? > You are not able to call test1 in presented form (you will get error: Incompatible types).

In that case, I do not understand at all what you are in fact trying to achieve :/

Because with the exception of the ^ operator, I see no need for any special constructions to achieve a "nullable type", except maybe some implicit constructor/destructor ?

Michael.

Reply by email

Maciej Izak
to Michael Van Canneyt

2016-07-28 14:59 GMT+02:00 Michael Van Canneyt :

Because with the exception of the ^ operator, I see no need for any special constructions to achieve a "nullable type", except maybe some implicit constructor/destructor ?

procedure with var parameter is special case, we need somehow to perform backward compatibility and strong typing is the must. In the fact TNullable<Integer> is proxy type to field of ^Integer type. ^ operator exist to make life easier in comparison to C# implementation.

"default field" is not necessary for nullable types but is extremely useful addition, more options and optimization for end user. We can exclude completely possibility of usage ^ operator for nullable types (^ for nullable type is just my invention). In that case calling functions like test1 will be impossible. Just pure copy of Nullable types from C# -,- . So you are right - ascetic version of nullable type need only management operators (aka implicit constructor/destructor). Presented nullable type is not "pure" copy of C# implementation, presented above implementation has Pascal spirit and optimizations impossible to achieve in other languages.

btw. you can add

class operator Equal(A: TNullable<T>; B: T): Boolean;

to run this:

if b = 0 then
  b:=1
else
  b:=23;
Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak
Maciej Izak wrote 14 lines
2016-07-28 14:59 GMT+02:00 Michael Van Canneyt : > Because with the exception of the ^ operator, I see no need for any special > constructions to achieve a "nullable type", except maybe some implicit > constructor/destructor ? > procedure with var parameter is special case, we need somehow to perform backward compatibility and strong typing is the must. In the fact TNullable<Integer> is proxy type to field of ^Integer type. ^ operator exist to make life easier in comparison to C# implementation. "default field" is not necessary for nullable types but is extremely useful addition, more options and optimization for end user.

Can you please explain this, because as far as I can see from your explanation, all that it does is make 2 assignment operators and maybe a typecast operator unnecessary ?

(in which case it falls under the category syntactic sugar, like "for .. in", which is fine for me)

Are there additional benefits I have missed ?

Show quoted text 8 lines
We can exclude completely possibility of usage ^ operator for nullable types (^ for nullable type is just my invention). In that case calling functions like test1 will be impossible. Just pure copy of Nullable types from C# -,- . So you are right - ascetic version of nullable type need only management operators (aka implicit constructor/destructor). Presented nullable type is not "pure" copy of C# implementation, presented above implementation has Pascal spirit and optimizations impossible to achieve in other languages.

Well, I think this addition is not necessary, maybe even dangerous. I can live with the 'ascetic' version :-)

Since the following:

Procedure SomeTest(Var X : Integer);
begin
   X:=1;
end;
Var
   A : TNullable<Integer>;
begin
   SomeTest(A^);
end.

Will lead to a crash on a null value, I don't think this is a very good idea to add. We should protect users from crashes, not expose them to it :-)

So, currently balance for me, if I understood everything correctly: [+] Implicit constructor/destructor.
Good for simulating 'managed' types.
[+] Default field (even if it is only syntactic sugar) [-] ^ dereference magic: a no-no :)

Michael.

Reply by email

Dmitry Boyarintsev
to Michael Van Canneyt

On Thu, Jul 28, 2016 at 10:37 AM, Michael Van Canneyt <

wrote: [+] Default field (even if it is only syntactic sugar)

can-o'worms:
Wasn't there a discussion to have multiple default fields depending on the type?

thanks,
Dmitry

Reply by email

Michael Van Canneyt
to Dmitry Boyarintsev
Dmitry Boyarintsev wrote 9 lines
On Thu, Jul 28, 2016 at 10:37 AM, Michael Van Canneyt < > wrote: > [+] Default field (even if it is only syntactic sugar) > can-o'worms: Wasn't there a discussion to have multiple default fields depending on the type?

[-] Multiple default fields: definite nono, since this compromises type safety.

Michael.

Reply by email

Stefan Glienke
to Michael Van Canneyt: Can you please explain this, because as far as I can see from your explanation, all that…
Show quoted text 27 lines
Well, I think this addition is not necessary, maybe even dangerous. I can live with the 'ascetic' version :-) Since the following: Procedure SomeTest(Var X : Integer); begin X:=1; end; Var A : TNullable<Integer>; begin SomeTest(A^); end. Will lead to a crash on a null value, I don't think this is a very good idea to add. We should protect users from crashes, not expose them to it :-) So, currently balance for me, if I understood everything correctly: [+] Implicit constructor/destructor. Good for simulating 'managed' types. [+] Default field (even if it is only syntactic sugar) [-] ^ dereference magic: a no-no :)

I agree with Michael here. While in Spring4D our nullables have the implicit operator in both directions in retrospective I think it's not a good idea and the nullable<x> -> x conversion should be explicit (with the possibility to raise an explicit exception - not an AV) and for sure not be faked by the deref operator because a nullable is not a reference type in my book even if it can be null (err nil?) That btw leads to another question - is the "null" state equal to nil - or is it something different. So a nullable is kind of both things, it can be nil/null but if not it contains the value but does not point to it thus cannot/should not be deferenced. C# for example requires a hard cast or the call to .Value to get the underlying value from a nullable with the possibility to throw a InvalidOperationException if it has no value.

FWIW if you make it a new language construct I actually would prefer "nullable of <type>" which is consistent with "array of <type>" or "set of <type>".

Reply by email

Maciej Izak
to Michael Van Canneyt: Can you please explain this, because as far as I can see from your explanation, all that…

2016-07-28 16:37 GMT+02:00 Michael Van Canneyt :

Are there additional benefits I have missed ?

Using nilable (I think nilable is better than nullable) records is terrible without default field:

=== code begin ===

var
  n: TNilable<TRec>;
  x: TRec;
begin
  {
   ... let say that n is assigned somewhere, assume that n.HasValue = true
  }
  // to change any field in nilable n  you need to...
  x := n.Value;
  x.foo1 := 10;
  x.foo2 := 'abc';
  n := x;
end.

=== code end ===

instead of:

=== code begin ===

var
  n: TNilable<TRec>;
begin
  {
   ... let say that n is assigned somewhere, assume that n.HasValue = true
  }
  x.foo1 := 10;
  x.foo2 := 'abc';
end.

=== code end ===

Show quoted text 20 lines
I can live with the 'ascetic' version :-) Since the following: Procedure SomeTest(Var X : Integer); begin X:=1; end; Var A : TNullable<Integer>; begin SomeTest(A^); end. Will lead to a crash on a null value, I don't think this is a very good idea to add. We should protect users from crashes, not expose them to it :-)

that will change nothing, just look below. Just harder to usage pure non-pascalish clone of C# struct (finally C# has pointers as nonstandard/unsafe type so feature with direct dereference is disabled by design).

=== code begin ===

procedure SomeTest(x : Integer);
begin
end;
Var
  A : TNullable<Integer>;
begin
  SomeTest(A.Value); // AV error here!
end.

=== code end ====

A^ is shortcut for A.Value but has advantage = direct dereference to Instance.

Best regards, Maciej Izak

Reply by email

Maciej Izak
to Maciej Izak

2016-07-28 17:49 GMT+02:00 Maciej Izak :

Show quoted text 16 lines
=== code begin === var n: TNilable<TRec>; begin { ... let say that n is assigned somewhere, assume that n.HasValue = true } x.foo1 := 10; x.foo2 := 'abc'; end. === code end ===

small typo. n instead of x ;)

Best regards, Maciej Izak

Reply by email

Michael Van Canneyt
to Maciej Izak: 2016-07-28 16:37 GMT+02:00 Michael Van Canneyt : Using nilable (I think nilable is better…
Maciej Izak wrote 21 lines
2016-07-28 16:37 GMT+02:00 Michael Van Canneyt : > > Are there additional benefits I have missed ? > > Using nilable (I think nilable is better than nullable) records is terrible without default field: === code begin === var n: TNilable<TRec>; x: TRec; begin { ... let say that n is assigned somewhere, assume that n.HasValue = true } // to change any field in nilable n you need to... x := n.Value;

OK, you convinced me for this one :-)

Michael.

Show quoted text 22 lines
that will change nothing, just look below. Just harder to usage pure non-pascalish clone of C# struct (finally C# has pointers as nonstandard/unsafe type so feature with direct dereference is disabled by design). === code begin === procedure SomeTest(x : Integer); begin end; Var A : TNullable<Integer>; begin SomeTest(A.Value); // AV error here! end. === code end ==== A^ is shortcut for A.Value but has advantage = direct dereference to Instance.

Here I am not convinced.

This is an artifact of your implementation: you use a pointer. There is no need to use a pointer. One can just as well do

TNullable <T> = Record
Private
   Fvalue : T;
   IsNotNull : Boolean;
Public
   // all the rest, including
   Property Value : T Read GetValue Write SetValue;
   Property IsNull : Boolean Read GetIsNull;
end;
Then you will not have this problem;
GetValue will always return a 'Default' value.

Michael.

Reply by email

Maciej Izak
to Michael Van Canneyt

2016-07-28 17:58 GMT+02:00 Michael Van Canneyt :

Then you will not have this problem; GetValue will always return a 'Default' value.

IMO bad design. You can't use value of not assigned nilable types as same as you can't use not assigned pointers >.<.

There is no magic in ^ . Just look at declaration:

Instance: ^T default;

you can create alternative nilable struct with:

FInstance: T default;
FHasValue: Boolean;

with that you don't need any ^ operator

Best regards, Maciej Izak

Reply by email

Maciej Izak
to Stefan Glienke: I agree with Michael here. While in Spring4D our nullables have the implicit operator in…

2016-07-28 17:03 GMT+02:00 Stefan Glienke :

Show quoted text 21 lines
I agree with Michael here. While in Spring4D our nullables have the implicit operator in both directions in retrospective I think it's not a good idea and the nullable<x> -> x conversion should be explicit (with the possibility to raise an explicit exception - not an AV) and for sure not be faked by the deref operator because a nullable is not a reference type in my book even if it can be null (err nil?) That btw leads to another question - is the "null" state equal to nil - or is it something different. So a nullable is kind of both things, it can be nil/null but if not it contains the value but does not point to it thus cannot/should not be deferenced. C# for example requires a hard cast or the call to .Value to get the underlying value from a nullable with the possibility to throw a InvalidOperationException if it has no value.

Remember that we working not in .NET environment, Pascal has assembler, pointers and other cool stuff, and I think that we need different approach adjusted to highly optimized needs. Explicit conversion still exist. Default field is blazing fast solution and it covers many needs (not nilable types only). I don't want 1:1 copy from C# -,-

Show quoted text 5 lines
FWIW if you make it a new language construct I actually would prefer "nullable of <type>" which is consistent with "array of <type>" or "set of <type>".

aside from the syntax I see nilable types as new type kind called: proxy. Same family as smart pointers and ARC objects, that means new unified TTypeData entry:

            tkProxy:
              (ProxyType: TProxyType;
               ProxyOwnerType: TypeInfoPtr; // maybe ProxyContainerType is
better name? It points to type information of record
               DefaultFieldOffset: PtrUInt;
               DefaultFieldType: TypeInfoPtr);

where

TProxyType = (
  ptCustom, // proxy object defined by user, declared as var x:
TMySmartPtr<Pointer>;
  ptNilable, // declared as var x: nilable/nullable Integer
  ptStrong, // declared as var x: strong TObject;
  ptWeak, // declared as var x: weak TObject;
  ptUnretained); // declared as var x: unretained TObject;
Best regards, Maciej Izak

Reply by email

Maciej Izak
to Sven Barth: First of let me tell you that in essence and in general I'm in support of your "default"…

2016-07-26 21:59 GMT+02:00 Sven Barth :

First of let me tell you that in essence and in general I'm in support of your "default" idea

Not only you Sven ;) I found interesting comment by Allen Bauer, seems like "default" idea is right direction. Completely independent source. I didn't know before about this comment. Comment created in May 2015 strictly related to nullable types. My work starts in 2014 and "default" in my case was created for smart pointers and ARC objects :)

Additional note: IMO 'default' for fields is more powerful and together with default for non-indexed properties is awesome combo.

=== begin quotation ===
Current working theory of Nullable<T>.

Nullable<T> = record
...
property Value: T read FValue; default;
...
end;

Using the default directive to "hoist" the operators of "T". Currently the default directive only works for array properties by "hoisting" the '[]' operator. Marking a non-array property with default will make the containing type behave as that type.

This, coupled with some intrinsic compiler knowledge of the Nullable<T> type will make Nullable<T> work without any addition of keywords or other standard functions or procedures.

Using the "default" directive on a non-array property will work for any type, except for having the null-propagation semantics.

When considering language features, I try and not only make it work for the intended purpose, but also broaden reach of any supporting feature. In the above scenario, even user-defined operators on "T" will be properly hoisted and used.
=== end quotation ===
source (one of last comments) :
https://plus.google.com/u/0/+HoracioJoseCavalcantiFilho/posts/2vzP8sdF4f7

Best regards, Maciej Izak

Reply by email