Discussion:
[fpc-pascal] Auto vars (again)
Ryan Joseph
2018-08-17 23:24:05 UTC
Permalink
I had some free time recently so I decided as a learning experience to fork the compiler and implement the “auto var” idea that was mentioned a few weeks ago.

What I found is that it’s a pretty lightweight (in terms of impact on the compiler) and unintrusive way to manage memory on a per-scope basis which solves a minor but common pattern. This was highly debated but I know from my personal experience I often know at the time of declaring a class that I will allocate the class at the top of the function and free at the end. If this was C++ I would declare the class on the stack but we don’t have that option in Pascal so we’re forced into either a class or record paradigm. The better option would probably be full blown ARC but I don’t know if that’s ever going to be on the agenda of FPC.

I understand there’s lots of potential problems like passing an auto var out of scope, classes with constructors that require parameters (rather big problem) or perhaps the “auto” modifier (I did that because it was easy to parse) but is there any merit to this idea if it was cleaned up and made safe? I did some tests to see if you could prevent passing auto vars out of scope and it’s possible to prevent most ways but not 100%.

========================================================

program auto_test_1;

type
TMyClass = class
data: TObject; auto;
end;

var
obj: TMyClass; auto;
begin
// obj is auto so TMyClass.Create is called along with subsequent auto var members
writeln('obj:', obj.classname);
writeln('data:', obj.data.classname);
// when TMyClass.Free is called auto var members call Free also
end.

program auto_test_2;
uses
fgl;

var
list: specialize TFPGList<integer>; auto;
i: integer;
begin
// list is auto so TFPGList<integer>.Create is called
// TFPGList as an auto var is a compelling alternative to dynamic arrays
list.Add(1);
list.Add(2);
list.Add(3);
for i in list do
writeln(i);
end.


Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.or
Maciej Izak
2018-08-18 01:50:16 UTC
Permalink
Post by Ryan Joseph
but is there any merit to this idea if it was cleaned up and made safe? I
did some tests to see if you could prevent passing auto vars out of scope
and it’s possible to prevent most ways but not 100%.
You are trying to achieve something what is almost possible with current
FPC trunk version (which is also safe in the case of exception):

=== code begin ===
TAutoCreate<T: class> = record
_: T;
class operator Initialize(var a: TAutoCreate<T>);
class operator Finalize(var a: TAutoCreate<T>);
class operator Copy(constref a: TAutoCreate<T>; var b: TAutoCreate<T>);
class operator AddRef(var a: TAutoCreate<T>);
end;

class operator TAutoCreate<T>.Initialize(var a: TAutoCreate<T>);
begin
a._ := T.Create;
end;

class operator TAutoCreate<T>.Finalize(var a: TAutoCreate<T>);
begin
a._.Free;
end;

class operator TAutoCreate<T>.Copy(constref a: TAutoCreate<T>;
var b: TAutoCreate<T>);
begin
// raise { some exception - prevent copy };
end;

class operator TAutoCreate<T>.AddRef(var a: TAutoCreate<T>);
begin
// raise { some exception - prevent copy };
end;

var
o: TAutoCreate<TObject>;
begin // auto Create
Writeln(o._.ToString);
end. // auto Free;
=== code end ===

the code above was not tested (written now) but should work. The only
disadvantage of above solution is lack of the way to omit "_".

The solution which probably should be accepted by fpc team is default
property without indexer:

=== code begin ===
property obj: T read fobj; default;
=== code end ===

with this property should be possible:

=== code begin ===
Writeln(o.ToString);
=== code end ===

next you can try to provide compiler magic:

=== code begin ===
var o: TObject auto; // equivalent of var o: TAutoCreate<TObject>;
// please note that syntax with semicolon is improper:
//var o: TObject; auto;
=== code end ===

anyway I am not big fan of "auto" because it means many troubles, what if:

* there is no parameter less constructor
* why to call constructor when there is NewInstance (this seems more proper
for this case but...)

after NewInstance you can (and rather this will be necessary) call selected
constructor from instance which will be executed like regular method, but
what is the sense of "auto" calling NewInstance when you still need to
execute Constructor? :D

I see rather no reason for this feature in compiler (but nice try :) ).
There are many other directions to solve "auto free" problems without
compiler modifications - maybe not all can be solved but many problems for
sure - for example you have ready to use TAutoFree from mORMot or initial
experiments with ARC objects for NewPascal (still worth to mention ;P).
--
Best regards,
Maciej Izak
Ryan Joseph
2018-08-18 17:04:29 UTC
Permalink
Post by Ryan Joseph
but is there any merit to this idea if it was cleaned up and made safe? I did some tests to see if you could prevent passing auto vars out of scope and it’s possible to prevent most ways but not 100%.
var
o: TAutoCreate<TObject>;
begin // auto Create
Writeln(o._.ToString);
end. // auto Free;
=== code end ===
the code above was not tested (written now) but should work. The only disadvantage of above solution is lack of the way to omit "_”.
I think you may be right about this. Having “auto” leverage the existing management operators makes sense. It’s more overhead to wrap everything in records though and the larger problem is the so-called syntactic sugar missing.
Post by Ryan Joseph
=== code begin ===
property obj: T read fobj; default;
=== code end ===
=== code begin ===
Writeln(o.ToString);
=== code end ===
How does that property work exactly? Making this work properly is the most important part but I never considered it. It sounds like “with” statements kind of.
Post by Ryan Joseph
=== code begin ===
var o: TObject auto; // equivalent of var o: TAutoCreate<TObject>;
//var o: TObject; auto;
=== code end ===
I wasn’t sure what’s proper. Function modifiers look like that but there are no var modifiers in Pascal as of now. The absolute keyword is kind of similar. We have “threadvar” also which sets a precedence and starts a new section which is nice. Either way I really like the idea of introducing a keyword instead of using the generic <> syntax.
Post by Ryan Joseph
* there is no parameter less constructor
That’s the biggest problem IMO. Calling NewInstance is correct, you’re right, but if the class needs parameters you need to call the constructor again anyways so it kind of defeats the purpose. When the class has only a parameterless constructor it works beautifully though so the feature is limited to those cases.
Post by Ryan Joseph
* why to call constructor when there is NewInstance (this seems more proper for this case but...)
after NewInstance you can (and rather this will be necessary) call selected constructor from instance which will be executed like regular method, but what is the sense of "auto" calling NewInstance when you still need to execute Constructor? :D
Agreed, that limits the usefulness of the feature. When it works well it’s a real win though.
Post by Ryan Joseph
I see rather no reason for this feature in compiler (but nice try :) ). There are many other directions to solve "auto free" problems without compiler modifications - maybe not all can be solved but many problems for sure - for example you have ready to use TAutoFree from mORMot or initial experiments with ARC objects for NewPascal (still worth to mention ;P).
How does TAutoFree in mORMot work? Never heard of this.

Full blown ARC is probably the best option but I was thinking of intermediate steps that are easy to implement and solve a a portion of the common use cases. It’s a poor mans ARC at best. ;)

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pasc
Marcos Douglas B. Santos
2018-08-18 18:26:52 UTC
Permalink
Post by Ryan Joseph
How does TAutoFree in mORMot work? Never heard of this.
See http://blog.synopse.info/post/2014/11/14/Automatic-TSQLRecord-memory-handling

regards,
Marcos Douglas
_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/li
Maciej Izak
2018-08-18 21:18:47 UTC
Permalink
Post by Marcos Douglas B. Santos
Post by Ryan Joseph
How does TAutoFree in mORMot work? Never heard of this.
See http://blog.synopse.info/post/2014/11/14/Automatic-
TSQLRecord-memory-handling
worth to note that in FPC you additionally need to use "with ... do" (or
use local variable IAutoFree), otherwise interface will be release
immediately and code will be broken. The example in blog needs to be
corrected:

=== code begin ===
function NewMaleBaby(Client: TSQLRest; const Name,Address: RawUTF8): TID;
var Baby: TSQLBaby;
begin
with TSQLBaby.AutoFree(Baby) do begin
Baby.Name := Name;
Baby.Address := Address;
Baby.BirthDate := Date;
Baby.Sex := sMale;
result := Client.Add(Baby);
end;
end;
=== code end ===

or you need to wait for {$MODESWITCH SCOPEDINTERFACEDESTROY} in NewPascal
to use examples AS-IS (with SCOPEDINTERFACEDESTROY on). To be honest I
forgot to send the patch with SCOPEDINTERFACEDESTROY to FPC when I had
occasion/access to FPC trunk ^^ - anyway I was the only one who want this
in the FPC core team for Delphi compatibility (and for large legacy
projects where is really hard to adjust code which depends on
SCOPEDINTERFACEDESTROY)... FPC core team said that this behavior of Delphi
is not documented and not COM compatible (or is undefined in COM - I don't
remember). Also there is some mystery example for large methods when Delphi
is able to break the rule and such interface is released earlier (but no
one was able to provide this example) - I was also trying without success,
so SCOPEDINTERFACEDESTROY seems really Delphi compatible.
--
Best regards,
Maciej Izak
Jonas Maebe
2018-08-19 10:39:40 UTC
Permalink
on SCOPEDINTERFACEDESTROY)... FPC core team said that this behavior of
Delphi is not documented and not COM compatible (or is undefined in COM
- I don't remember). Also there is some mystery example for large
methods when Delphi is able to break the rule and such interface is
released earlier (but no one was able to provide this example)
Here is one: https://bugs.freepascal.org/view.php?id=6035#c28656


Jonas
_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fp

Ryan Joseph
2018-08-18 22:26:41 UTC
Permalink
Post by Ryan Joseph
Post by Maciej Izak
=== code begin ===
property obj: T read fobj; default;
=== code end ===
=== code begin ===
Writeln(o.ToString);
=== code end ===
How does that property work exactly? Making this work properly is the most important part but I never considered it. It sounds like “with” statements kind of.
Ok, I understand what you mean now. This is actually a really great idea for all sorts of other uses because it helps to unify namespaces between classes and prevent long.and.annoying() chaining of fields.

Btw why should this be a property? I know [] properties use the default keyword also but I don’t understand where this came from and it’s strange to name the [] property since the name is irrelevant. I would think you’d want to just put a modifier on the default field:

TAutoCreate<T: class> = record
_: T; default;
class operator Initialize(var a: TAutoCreate<T>);
class operator Finalize(var a: TAutoCreate<T>);
class operator Copy(constref a: TAutoCreate<T>; var b: TAutoCreate<T>);
class operator AddRef(var a: TAutoCreate<T>);
end;

just curious and I thought I’d ask.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mail
Ryan Joseph
2018-08-18 18:33:54 UTC
Permalink
Post by Maciej Izak
class operator Initialize(var a: TAutoCreate<T>);
class operator Finalize(var a: TAutoCreate<T>);
One other thing I wanted to ask you. I know it’s minor but for me it’s pretty disappointing that the Initialize and Finalize operators aren’t simply constructor/destructor since those names are available in records already (constructor with no parameter that is).

I say this because the syntax is long and hard to remember and don’t follow the usual convention of constructors/destructors. Initialize and Finalize are the most common operators you implement so it makes sense they would follow the normal syntax rules (c++ does this for classes declared on the stack).

Was there any consideration to making the default constructor and destructor be used instead of class operators?

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.fre
Loading...