Sign in Join

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

Mailing lists / fpc-pascal / 2018-12

Implicit generic specializations

14 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

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

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

Reply by email

denisgolovan
to Ryan Joseph

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

Reply by email

Sven Barth
to denisgolovan

Am So., 2. Dez. 2018, 10:55 hat denisgolovan
geschrieben:

Show quoted text 7 lines
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

Reply by email

Ryan Joseph
to Sven Barth: Am So., 2. Dez. 2018, 10:55 hat denisgolovan geschrieben: Where is it messy? O.o Also the…
On Dec 2, 2018, at 8:11 PM, Sven Barth via fpc-pascal 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

Reply by email

Ryan Joseph
to 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

Reply by email

Sven Barth
to Ryan Joseph: I did this wrong then because the generic takes precedence now. Why shouldn’t the…

Am So., 2. Dez. 2018, 15:28 hat Ryan Joseph
geschrieben:

Show quoted text 14 lines
> On Dec 2, 2018, at 8:11 PM, Sven Barth via fpc-pascal < > 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

Reply by email

Ryan Joseph
to Sven Barth
Show quoted text 5 lines
On Dec 2, 2018, at 10:53 PM, Sven Barth via fpc-pascal 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

Reply by email

Sven Barth
to Ryan Joseph

Am Mo., 3. Dez. 2018, 03:45 hat Ryan Joseph
geschrieben:

Show quoted text 17 lines
> On Dec 2, 2018, at 10:53 PM, Sven Barth via fpc-pascal < > 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

Reply by email

Ryan Joseph
to Sven Barth
On Dec 3, 2018, at 1:59 PM, Sven Barth via fpc-pascal 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

Reply by email

Ryan Joseph
to Ryan Joseph
On Dec 3, 2018, at 2:45 PM, Ryan Joseph 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

Reply by email

Martok
to Ryan Joseph
Ryan Joseph wrote
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

Reply by email

Ryan Joseph
to Martok
Show quoted text 11 lines
On Dec 3, 2018, at 9:10 PM, Martok 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

Reply by email

Sven Barth
to Ryan Joseph: I believe I managed to solve the problem and now non-generic procedures take precedence.…
Ryan Joseph wrote 5 lines
> On Dec 3, 2018, at 2:45 PM, Ryan Joseph 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

Reply by email