Discussion:
"Default" discussion for SmartPointers etc
(too old to reply)
Maciej Izak
2016-07-26 12:51:12 UTC
Permalink
Raw Message
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
Michael Van Canneyt
2016-07-26 13:09:06 UTC
Permalink
Raw Message
Post by 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
"If all fails - try to use default field".
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
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
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Sven Barth
2016-07-26 19:59:04 UTC
Permalink
Raw Message
Post by 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.
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. :)
Post by Maciej Izak
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.
Post by Maciej Izak
The "default" modifier is inspired by "default" modifier for indexed
properties. All of my work is (I hope so) natural for Pascal language.
"If all fails - try to use default field".
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"?
Post by Maciej Izak
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
https://github.com/maciej-izak/PascalSmartPointers/tree/master/tests
Note: tests for visibility.
Post by Maciej Izak
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
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
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-27 08:12:38 UTC
Permalink
Raw Message
Post by Sven Barth
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...
Post by Sven Barth
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 ;)
Post by Sven Barth
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).
Post by Sven Barth
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".
Post by Sven Barth
Post by Maciej Izak
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
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.
Post by Sven Barth
Post by Maciej Izak
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
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.
Post by Sven Barth
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
Post by Sven Barth
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.
Post by Sven Barth
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.
Post by Sven Barth
Note: don't forget to test with global operator overloads ;)
I will try to remember ;)
--
Best regards,
Maciej Izak
Maciej Izak
2016-07-27 08:29:11 UTC
Permalink
Raw Message
Post by 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
Michael Van Canneyt
2016-07-27 08:38:00 UTC
Permalink
Raw Message
Post by Maciej Izak
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.
Post by Maciej Izak
Post by Sven Barth
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.
Post by Maciej Izak
@@ operator is very simple way to determine where you point. In any other
In practice works like a charm and is very clear.
Post by Sven Barth
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
tdefault7.pp). In daily usage it works very well and any other compiler
behavior is irrational.
? A compiler always behaves rational.

Michael.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-27 08:51:40 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
? A compiler always behaves rational.
Not Delphi compiler ;). Check new intrinsic "IsManaged" for XE7 and newer ;)
--
Best regards,
Maciej Izak
Jonas Maebe
2016-07-27 09:24:59 UTC
Permalink
Raw Message
Post by Maciej Izak
@@ operator is very simple way to determine where you point. In any other
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
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-27 11:05:38 UTC
Permalink
Raw Message
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
Michael Van Canneyt
2016-07-27 11:11:05 UTC
Permalink
Raw Message
Post by Maciej Izak
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
=== 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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-27 12:12:12 UTC
Permalink
Raw Message
Post by 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
Michael Van Canneyt
2016-07-27 12:18:42 UTC
Permalink
Raw Message
Post by Maciej Izak
Post by Michael Van Canneyt
Instance: ^T; default;
";" between default and type will not work.
Sorry, typo :/
Post by Maciej Izak
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Jonas Maebe
2016-07-27 13:33:12 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
Post by Michael Van Canneyt
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
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Michael Van Canneyt
2016-07-27 14:03:29 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
Post by Michael Van Canneyt
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.
address of the first character, but of the variable itself. Similarly, there
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
(https://github.com/maciej-izak/PascalSmartPointers/blob/master/tests/tdefault21.pp
).
Why not introduce an address operator ?

operator @ (T : MyType) : Pointer;

Michael.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Jonas Maebe
2016-07-27 14:12:55 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
Why not introduce an address operator ?
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
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Michael Van Canneyt
2016-07-27 14:42:54 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
Why not introduce an address operator ?
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Jonas Maebe
2016-07-27 14:58:53 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
Post by Jonas Maebe
Post by Michael Van Canneyt
Why not introduce an address operator ?
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).
Post by Michael Van Canneyt
Knowing the purpose, what do you think can be done ?
I wrote some ideas in the original email you replied to.


Jonas
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-27 19:04:24 UTC
Permalink
Raw Message
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
Sven Barth
2016-07-27 20:47:09 UTC
Permalink
Raw Message
Post by Maciej Izak
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]").
Post by 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();
Post by Maciej Izak
=== 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
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
Jonas Maebe
2016-07-27 21:27:21 UTC
Permalink
Raw Message
Post by Sven Barth
Post by 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.
Post by Sven Barth
Post by Maciej Izak
=== 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
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
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Martin Schreiber
2016-07-28 06:05:41 UTC
Permalink
Raw Message
Post by Jonas Maebe
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
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Michael Van Canneyt
2016-07-28 06:21:28 UTC
Permalink
Raw Message
Post by Jonas Maebe
Post by Sven Barth
Post by 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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Sven Barth
2016-07-28 09:42:44 UTC
Permalink
Raw Message
Post by 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).

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
Michael Van Canneyt
2016-07-28 10:40:22 UTC
Permalink
Raw Message
Post by Jonas Maebe
Post by 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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Stefan Glienke
2016-07-28 11:02:31 UTC
Permalink
Raw Message
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)
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-28 12:02:02 UTC
Permalink
Raw Message
Post by Stefan Glienke
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
Maciej Izak
2016-07-28 11:20:32 UTC
Permalink
Raw Message
Post by 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
Michael Van Canneyt
2016-07-28 11:30:52 UTC
Permalink
Raw Message
Post by Maciej Izak
Post by 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.
Post by Maciej Izak
=== 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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-28 11:51:04 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
test1(a);
That won't compile.
Post by Michael Van Canneyt
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).
Post by Michael Van Canneyt
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 ?
Post by Michael Van Canneyt
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
Michael Van Canneyt
2016-07-28 12:59:19 UTC
Permalink
Raw Message
Post by Maciej Izak
Post by Michael Van Canneyt
test1(a);
That won't compile.
Post by Michael Van Canneyt
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 ?
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-28 14:05:34 UTC
Permalink
Raw Message
Post by 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
Michael Van Canneyt
2016-07-28 14:37:37 UTC
Permalink
Raw Message
Post by Maciej Izak
Post by 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 ?
Post by Maciej Izak
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Dmitry Boyarintsev
2016-07-28 14:41:42 UTC
Permalink
Raw Message
On Thu, Jul 28, 2016 at 10:37 AM, Michael Van Canneyt <
Post by Michael Van Canneyt
[+] 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
Michael Van Canneyt
2016-07-28 14:53:23 UTC
Permalink
Raw Message
Post by Dmitry Boyarintsev
On Thu, Jul 28, 2016 at 10:37 AM, Michael Van Canneyt <
Post by Michael Van Canneyt
[+] Default field (even if it is only syntactic sugar)
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Stefan Glienke
2016-07-28 15:03:07 UTC
Permalink
Raw Message
Post by Michael Van Canneyt
Well, I think this addition is not necessary, maybe even dangerous.
I can live with the 'ascetic' version :-)
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 :-)
[+] 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>".
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-28 16:47:50 UTC
Permalink
Raw Message
Post by Stefan Glienke
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# -,-
Post by Stefan Glienke
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
Maciej Izak
2016-07-28 15:49:26 UTC
Permalink
Raw Message
Post by 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 ===
Post by Michael Van Canneyt
I can live with the 'ascetic' version :-)
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
Maciej Izak
2016-07-28 15:57:12 UTC
Permalink
Raw Message
Post by Sven Barth
=== 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
Michael Van Canneyt
2016-07-28 15:58:55 UTC
Permalink
Raw Message
Post by Maciej Izak
Post by Michael Van Canneyt
Are there additional benefits I have missed ?
Using nilable (I think nilable is better than nullable) records is terrible
=== 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.
Post by Maciej Izak
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.
_______________________________________________
fpc-devel maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
Maciej Izak
2016-07-28 16:09:14 UTC
Permalink
Raw Message
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
Maciej Izak
2016-07-28 11:07:45 UTC
Permalink
Raw Message
...That's where Maciej's idea for the "default" keyword came from, cause
that allows something similar for array properties...
That is it! Thanks for detailed descriptions for others. All is expressed
in right way.
--
Best regards,
Maciej Izak
Sven Barth
2016-07-27 12:28:59 UTC
Permalink
Raw Message
Post by Maciej Izak
Post by 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
Maciej Izak
2016-08-01 21:30:53 UTC
Permalink
Raw Message
Post by 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
Loading...