Discussion:
[fpc-pascal] Implicit generic specializations
Ryan Joseph
2018-12-02 09:13:15 UTC
Permalink
Since I had was already working on generics I wanted to see if I could implement the inferred specializations like is available in Delphi. The current syntax is too verbose to be usable so this is a necessary improvement in my opinion.

Here’s a first draft which seems to be working. There’s probably other ways to do the inference (I don’t have Delphi to test there’s) but this what I did for now. The algorithm basically scans params in order of appearance and inserts unique non-repeating types. For example:

(1,'string') = <Integer,String>
(1,2,3,4,5,6) = <Integer>
('a','b') = <String>
('string',1) = <String,Integer>
('a',1,'b',2,'c') = <String,Integer>


https://github.com/genericptr/freepascal/tree/generic_implicit


{$mode objfpc}
{$modeswitch implicitgenerics}

program test;

generic procedure DoThis<T>(msg:T);
begin
writeln('DoThis$1#1:',msg);
end;

generic procedure DoThis<T>(msg:T;param1:T);
begin
writeln('DoThis$1#2:',msg,' ',param1);
end;

generic procedure DoThis<T,U>(msg:T);
begin
writeln('DoThis$2#1:',msg);
end;

generic procedure DoThis<T,U>(msg:T;param1:U);
begin
writeln('DoThis$2#2:',msg,' ',param1);
end;

generic procedure DoThis<T,U>(msg:T;param1:U;param2:tobject);
begin
writeln('DoThis$2#3:',msg,' ',param1,' ',param2.classname);
end;

begin
DoThis(1); // DoThis$1#1:1
DoThis(1,1); // DoThis$1#2:1 1
DoThis('a','a'); // DoThis$1#2:a a
DoThis('a',1); // DoThis$2#2:a 1
DoThis('a',1,tobject.create); // DoThis$2#3:a 1 TObject
end.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi
denisgolovan
2018-12-02 09:55:17 UTC
Permalink
Hi Ryan

That's definitely a nice feature.
Could you clarify and/or discuss with compiler devs the rules for function overloads?
Currently FPC seems a bit messy even without inferencing like that.


BR,
Denis
_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/c
Sven Barth via fpc-pascal
2018-12-02 13:11:01 UTC
Permalink
Post by denisgolovan
Hi Ryan
That's definitely a nice feature.
Could you clarify and/or discuss with compiler devs the rules for function overloads?
Currently FPC seems a bit messy even without inferencing like that.
Where is it messy? O.o

Also the idea should be that a non-generic routine takes precedence.

Regards,
Sven
denisgolovan
2018-12-02 13:55:51 UTC
Permalink
Hi Sven
Post by Sven Barth via fpc-pascal
Where is it messy? O.o
See https://bugs.freepascal.org/view.php?id=28824
Post by Sven Barth via fpc-pascal
Also the idea should be that a non-generic routine takes precedence.
Seems reasonable.
--
Regards,
Denis Golovan
_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-b
Ryan Joseph
2018-12-02 13:57:44 UTC
Permalink
Post by Sven Barth via fpc-pascal
Also the idea should be that a non-generic routine takes precedence.
I did this wrong then because the generic takes precedence now. Why shouldn’t the generic take precedence though? It comes after so you think it would hide the first one. The symbol “DoThis” is now a generic dummy (compiler term) so I didn’t know you could even get access to the first DoThis.

procedure DoThis(msg:integer);
begin
writeln('DoThis:',msg);
end;

generic procedure DoThis<T>(msg:T);
begin
writeln('DoThis$1:',msg);
end;

begin
DoThis(1); // DoThis$1:1
end.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://li
Ryan Joseph
2018-12-02 14:35:20 UTC
Permalink
I just tested and my code has the same behavior as the explicit specialization (as expected), i.e. last wins, regardless of generic status.

procedure DoThis(msg:integer);
begin
writeln('DoThis:',msg);
end;

generic procedure DoThis<T>(msg:T);
begin
writeln('DoThis$1#1:',msg);
end;

begin
specialize DoThis<integer>(1); // DoThis$1#1:1
end.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mail
Sven Barth via fpc-pascal
2018-12-02 15:53:02 UTC
Permalink
Post by Ryan Joseph
On Dec 2, 2018, at 8:11 PM, Sven Barth via fpc-pascal <
Also the idea should be that a non-generic routine takes precedence.
I did this wrong then because the generic takes precedence now. Why
shouldn’t the generic take precedence though? It comes after so you think
it would hide the first one. The symbol “DoThis” is now a generic dummy
(compiler term) so I didn’t know you could even get access to the first
DoThis.
Specialization is expensive. If specialization can be avoided, it should
be. Not to mention that the non-generic one could have more optimized code.
Though to be sure I'll test with Delphi, we'll have to be compatible there
anyway.

Regards,
Sven
Ryan Joseph
2018-12-03 02:14:17 UTC
Permalink
Specialization is expensive. If specialization can be avoided, it should be. Not to mention that the non-generic one could have more optimized code.
Though to be sure I'll test with Delphi, we'll have to be compatible there anyway.
I think the “dummy” sym which is added after the generic procedure is overwriting the existing symbols. In the example below (my code disabled now) DoThis gives an error because it thinks DoThis is the dummy sym. If we want this to work the dummy needs to keep track of existing procsyms, unless there’s another way to get that information?

Personally I’m fine with this because I didn’t expect to be mixing generic procedures anyways.

procedure DoThis(msg:integer);
begin
writeln('DoThis:',msg);
end;

procedure DoThis(msg:string);
begin
writeln('DoThis:',msg);
end;

generic procedure DoThis<T>(msg:T);
begin
writeln('DoThis$1:',msg);
end;

begin
DoThis('a’); // ERROR: "Generics without specialization cannot be used as a type for a variable"
end.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists
Sven Barth via fpc-pascal
2018-12-03 06:59:11 UTC
Permalink
Post by Sven Barth via fpc-pascal
On Dec 2, 2018, at 10:53 PM, Sven Barth via fpc-pascal <
Specialization is expensive. If specialization can be avoided, it should
be. Not to mention that the non-generic one could have more optimized code.
Though to be sure I'll test with Delphi, we'll have to be compatible
there anyway.
I think the “dummy” sym which is added after the generic procedure is
overwriting the existing symbols. In the example below (my code disabled
now) DoThis gives an error because it thinks DoThis is the dummy sym. If we
want this to work the dummy needs to keep track of existing procsyms,
unless there’s another way to get that information?
The dummy symbol should only be created if there isn't an existing symbol
with that name. So maybe something is buggy there. (Also the dummy symbol
should be used for a non-generic routine if the order of declaration is the
other way round)

Regards,
Sven
Ryan Joseph
2018-12-03 07:45:18 UTC
Permalink
The dummy symbol should only be created if there isn't an existing symbol with that name. So maybe something is buggy there. (Also the dummy symbol should be used for a non-generic routine if the order of declaration is the other way round)
I just looked it over and I was wrong about the dummy, it’s just a flag. If the generic doesn’t cover existing functions then that messes up some assumptions I made so I need re-think the design now.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pas
Ryan Joseph
2018-12-03 13:01:54 UTC
Permalink
Post by Ryan Joseph
I just looked it over and I was wrong about the dummy, it’s just a flag. If the generic doesn’t cover existing functions then that messes up some assumptions I made so I need re-think the design now.
I believe I managed to solve the problem and now non-generic procedures take precedence. I guess it’s possible that you could opt out of an implicit specialization now but declaring and overload which the specific type you were interested in. This is probably a good fallback to have so it’s good it’s like this now.

{$mode objfpc}
{$modeswitch implicitgenerics}

program gi_implicit_overload;

procedure DoThis(msg:integer);overload;
begin
writeln('DoThis:',msg);
end;

generic procedure DoThis<T>(msg:T);overload;
begin
writeln('DoThis$1:',msg);
end;

begin
DoThis(1); // DoThis:1
DoThis('string’); // DoThis$1:string
end.


Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.o
Martok
2018-12-03 14:10:16 UTC
Permalink
Post by Ryan Joseph
I believe I managed to solve the problem and now non-generic procedures take precedence. I guess it’s possible that you could opt out of an implicit specialization now but declaring and overload which the specific type you were interested in. This is probably a good fallback to have so it’s good it’s like this now.
What happens when there are implicit conversion operators defined?
I.e.:

operator := (x: integer): string;
// with and without this more specific overload:
procedure DoThis(msg:integer);overload;
generic procedure DoThis<T>(msg:T);overload;

DoThis(42);

I'd normally say it should take the integer one (or specialize using integer)
and ignore the overloads, but now that I think about it, overloads should be
checked if they are required to satisfy type constraints on the generic, such as:
operator :=(x: integer): TObject; // whatever that might do
generic procedure DoThis<T: class>(inst: T);
DoThis(42);
--
Regards,
Martok


_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http:/
Ryan Joseph
2018-12-03 14:17:18 UTC
Permalink
Post by Martok
What happens when there are implicit conversion operators defined?
operator := (x: integer): string;
procedure DoThis(msg:integer);overload;
generic procedure DoThis<T>(msg:T);overload;
DoThis(42);
How is the assignment operator involved here? Maybe post full code.

Regards,
Ryan Joseph

_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://li
Sven Barth via fpc-pascal
2018-12-06 06:37:14 UTC
Permalink
Post by Ryan Joseph
Post by Ryan Joseph
I just looked it over and I was wrong about the dummy, it’s just a flag. If the generic doesn’t cover existing functions then that messes up some assumptions I made so I need re-think the design now.
I believe I managed to solve the problem and now non-generic procedures take precedence. I guess it’s possible that you could opt out of an implicit specialization now but declaring and overload which the specific type you were interested in. This is probably a good fallback to have so it’s good it’s like this now.
Good. I also confirmed this behavior with Delphi:

=== code begin ===

program GenTest;

type
   TSomeRecord = record
    Value: Integer;
    class procedure Test(aArg: String); overload; static;
    class procedure Test<T>(aArg: T); overload; static;
    class procedure Test(aArg: LongInt); overload; static;
  end;

class procedure TSomeRecord.Test(aArg: String);
begin
  Writeln('String');
end;

class procedure TSomeRecord.Test<T>(aArg: T);
begin
  Writeln('T');
end;

class procedure TSomeRecord.Test(aArg: LongInt);
begin
  Writeln('LongInt');
end;

begin
  TSomeRecord.Test('Hello World');
  TSomeRecord.Test(42);
  TSomeRecord.Test(True);
end.

=== code end ===

This will output:

=== output begin ===

String
LongInt
T

=== output end ===

Regards,
Sven
_______________________________________________
fpc-pascal maillist - fpc-***@lists.freepascal.org
http://lists.freepascal.org/cgi-bi

Loading...