Implicit generic specializations

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

Implicit generic specializations

Ryan Joseph
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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

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.


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

Re: Implicit generic specializations

Free Pascal - General mailing list
Am So., 2. Dez. 2018, 10:55 hat denisgolovan <[hidden email]> geschrieben:
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 

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

Re: Implicit generic specializations

denisgolovan
Hi Sven

> Where is it messy? O.o

See https://bugs.freepascal.org/view.php?id=28824
 
> Also the idea should be that a non-generic routine takes precedence.

Seems reasonable.

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

Re: Implicit generic specializations

Ryan Joseph
In reply to this post by Free Pascal - General mailing list


> On Dec 2, 2018, at 8:11 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> 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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

Ryan Joseph
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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

Free Pascal - General mailing list
In reply to this post by Ryan Joseph
Am So., 2. Dez. 2018, 15:28 hat Ryan Joseph <[hidden email]> geschrieben:


> On Dec 2, 2018, at 8:11 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> 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 

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

Re: Implicit generic specializations

Ryan Joseph


> On Dec 2, 2018, at 10:53 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> 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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

Free Pascal - General mailing list
Am Mo., 3. Dez. 2018, 03:45 hat Ryan Joseph <[hidden email]> geschrieben:


> On Dec 2, 2018, at 10:53 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> 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 

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

Re: Implicit generic specializations

Ryan Joseph


> On Dec 3, 2018, at 1:59 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> 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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

Ryan Joseph


> On Dec 3, 2018, at 2:45 PM, Ryan Joseph <[hidden email]> wrote:
>
> 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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

Martok
Am 03.12.2018 um 14:01 schrieb 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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: Implicit generic specializations

Ryan Joseph


> On Dec 3, 2018, at 9:10 PM, Martok <[hidden email]> wrote:
>
> 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);

How is the assignment operator involved here? Maybe post full code.

Regards,
        Ryan Joseph

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

Re: Implicit generic specializations

Free Pascal - General mailing list
In reply to this post by Ryan Joseph
Am 03.12.2018 um 14:01 schrieb Ryan Joseph:
>
>> On Dec 3, 2018, at 2:45 PM, Ryan Joseph <[hidden email]> wrote:
>>
>> 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  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal