Recent

Author Topic: Generics without specialization cannot be used as a type for a variable  (Read 8349 times)

Nitorami

  • Sr. Member
  • ****
  • Posts: 481
I am making a small library for Matrix operations. Because many elementary Matrix functions are identical for real and complex numbers, I try using generics.
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$modeswitch advancedrecords}
  3.  
  4. type generic TMatrix <zz> = record
  5. ...
  6. end;

Functions/procedures to manipulate the matrix elements would be implemented inside the record, but others using several matrices should more logially be outside, such as
Code: Pascal  [Select][+][-]
  1. function mul (A,B: TMatrix): TMatrix.

That throws an error "Generics without specialization cannot be used as a type for a variable". Is there a way round ?




Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: Generics without specialization cannot be used as a type for a variable
« Reply #1 on: February 18, 2019, 08:18:29 pm »
No. Neither mode delphi nor mode objfpc.
(I am also a bit bemused on how you would perform the mul in a generic way..... :) it simply needs overloads anyway)
Here's a pointer:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. type
  3. TComplex = record
  4.   a,b:double; // not real
  5. end;
  6.  
  7. operator * (const a,b: TComplex): TComplex;
  8. begin
  9.    result.a := a.a * b.a;
  10.    result.b := a.b * b.b;
  11. end;
  12.  
  13. begin
  14. end.

I believe there is already a complex unit around somewhere....Otherwise it is a doddle to write...
« Last Edit: February 18, 2019, 08:41:05 pm by Thaddy »
Specialize a type, not a var.

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Generics without specialization cannot be used as a type for a variable
« Reply #2 on: February 18, 2019, 11:03:27 pm »
Yes there is already a Complex unit.
The only true wisdom is knowing you know nothing

Blaazen

  • Hero Member
  • *****
  • Posts: 3237
  • POKE 54296,15
    • Eye-Candy Controls
Re: Generics without specialization cannot be used as a type for a variable
« Reply #3 on: February 18, 2019, 11:21:44 pm »
Its name is UComplex.
Lazarus 2.3.0 (rev main-2_3-2863...) FPC 3.3.1 x86_64-linux-qt Chakra, Qt 4.8.7/5.13.2, Plasma 5.17.3
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.21

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

Kays

  • Hero Member
  • *****
  • Posts: 569
  • Whasup!?
    • KaiBurghardt.de
Re: Generics without specialization cannot be used as a type for a variable
« Reply #4 on: February 18, 2019, 11:51:33 pm »
[…]
Code: Pascal  [Select][+][-]
  1. function mul (A,B: TMatrix): TMatrix.
[…]
Your type is not TMatrix but its specialization. Here's what I mean:
Code: Pascal  [Select][+][-]
  1. unit genericMatrix;
  2.  
  3. {$mode objFPC}
  4.  
  5. interface
  6.  
  7. type
  8.         generic matrix<zz> = object
  9.                 public
  10.                         function multiplyWith(const B: specialize matrix<zz>): specialize matrix<zz>;
  11.         end;
  12.  
  13. implementation
  14.  
  15. function matrix.multiplyWith(const B: specialize matrix<zz>): specialize matrix<zz>;
  16. begin
  17. end;
  18.  
  19. end.
You can even shorten the whole stuff:
Code: Pascal  [Select][+][-]
  1. unit genericMatrix;
  2.  
  3. {$mode objFPC}
  4.  
  5. interface
  6.  
  7. type
  8.         generic matrix<zz> = object
  9.                 public
  10.                         type
  11.                                 specialization = specialize matrix<zz>;
  12.                 public
  13.                         function multiplyWith(const B: specialization): specialization;
  14.         end;
  15.  
  16. implementation
  17.  
  18. function matrix.multiplyWith(const B: specialization): specialization;
  19. begin
  20. end;
  21.  
  22. end.
It's important you formally end a public section, otherwise the type isn't known.

Its name is UComplex.
For the sake of completeness, there are other options, see complex number § “see also”
« Last Edit: February 18, 2019, 11:56:20 pm by Kays »
Yours Sincerely
Kai Burghardt

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: Generics without specialization cannot be used as a type for a variable
« Reply #5 on: February 19, 2019, 08:01:13 am »
Yes but you want to specialize your TMatrix to real too. In that case you run into trouble in the implementation....As I wrote before. Generics are here really not the proper solution even if you could write a complex multiply implementation based on RTTI and I wonder if that is even possible. The compiler message indicates that it is not possible anyway.
« Last Edit: February 19, 2019, 08:10:44 am by Thaddy »
Specialize a type, not a var.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Generics without specialization cannot be used as a type for a variable
« Reply #6 on: February 19, 2019, 09:46:41 am »
I am making a small library for Matrix operations. Because many elementary Matrix functions are identical for real and complex numbers, I try using generics.
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$modeswitch advancedrecords}
  3.  
  4. type generic TMatrix <zz> = record
  5. ...
  6. end;

Functions/procedures to manipulate the matrix elements would be implemented inside the record, but others using several matrices should more logially be outside, such as
Code: Pascal  [Select][+][-]
  1. function mul (A,B: TMatrix): TMatrix.

That throws an error "Generics without specialization cannot be used as a type for a variable". Is there a way round ?

Either make your mul function part of TMatrix (e.g. as a class function) or use a generic function (requires FPC 3.2 or newer):
Code: Pascal  [Select][+][-]
  1. generic function mul<zz>(A, B: specialize TMatrix<zz>): specialize TMatrix<zz>;
  2. begin
  3.   // ...
  4. end;
  5.  
You'd then need to use it like this:
Code: Pascal  [Select][+][-]
  1. MyLongIntMatrixC := specialize mul<LongInt>(MyLongIntMatrixA, MyLongIntMatrixB);
  2.  
For mode Delphi simply remove the generic and specialize keywords.
In the future inference of type parameters might be possible (in both modes):
Code: Pascal  [Select][+][-]
  1. MyLongIntMatrixC := mul(MyLongIntMatrixA, MyLongIntMatrixB);
  2.  

Yes but you want to specialize your TMatrix to real too. In that case you run into trouble in the implementation....As I wrote before. Generics are here really not the proper solution even if you could write a complex multiply implementation based on RTTI and I wonder if that is even possible. The compiler message indicates that it is not possible anyway.
FPC's generics are more like C++'s templates than C#'s generics (in both modes), so as long as he only specializes with types that support all required operators (mainly "+" and "*") then it should work.

Nitorami

  • Sr. Member
  • ****
  • Posts: 481
Re: Generics without specialization cannot be used as a type for a variable
« Reply #7 on: February 20, 2019, 08:22:22 pm »
Hi all
Thanks for your thoughts. I begin to see why this is not as trivial as I thought.

It's clear I will need overloading at some point, but am also a bit obsessed  by the idea to minimize duplicated code, and still think it should be possible to define all elementary matrix function as generic (at least those affecting only the topology of the matrix, e.g. row deletion, transpose etc.), then specialize to real and complex, and add arithmetic functions in these specialized types by helpers.

This may hardly be worth it, so I'd try to move as many functions as possible to the generic declaration, particularly those which return a simple float or boolean, e.g. norms, condition, size etc.

At this point I am currently stuck - the compiler says it cannot determine which overloaded version of abs() to use in the simple code below. Why ? What seems to be the problem ?

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$modeswitch advancedrecords}
  3.  
  4. type generic TGenMat<zz> = record
  5.   public
  6.     data: zz;
  7.     function test: boolean;
  8. end;
  9.  
  10. function TGenMat.test: boolean;
  11. begin
  12.   result := abs(data) > 1;
  13. end;
  14.  
  15. type TRealMat = specialize TGenMat<single>;
  16.  
  17. begin
  18. end.
  19.  





« Last Edit: February 20, 2019, 09:18:11 pm by Nitorami »

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Generics without specialization cannot be used as a type for a variable
« Reply #8 on: February 20, 2019, 11:26:18 pm »
it may not or even help you but try casting the entry value of ABS(zz(data));

The only true wisdom is knowing you know nothing

Nitorami

  • Sr. Member
  • ****
  • Posts: 481
Re: Generics without specialization cannot be used as a type for a variable
« Reply #9 on: February 21, 2019, 05:29:06 am »
I just got out of bed because I had the same idea but it does not work. Only casting to a known type helps, but that defeats the purpose of generics. So... back to bed. :D

Nitorami

  • Sr. Member
  • ****
  • Posts: 481
Re: Generics without specialization cannot be used as a type for a variable
« Reply #10 on: February 21, 2019, 07:50:19 pm »
Well, after a lot of tinkering - it does not work. Within generics, the compiler seems to be unable to determine which overloaded function to call. It is impossible to use abs, sqr, sqrt, sin etc. on the generic variable.

But it works with operators. The compiler has no problem to select the correct function related to the '+' operator, as in the code below. Very odd.

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$modeswitch advancedrecords}
  3.  
  4. type complex = record re,im: real; end;
  5.  
  6. operator + (a,b: complex): complex; overload;
  7. begin
  8.   result.re := a.re + b.re;
  9.   result.im := a.im + b.im;
  10. end;
  11.  
  12.  
  13. type generic TGenMat <zz> = record
  14.   data: zz;
  15.   function test: zz;
  16. end;
  17.  
  18.  
  19. function TGenMat.test: zz;
  20. begin
  21.   result := data+data;  //this works
  22. end;
  23.  
  24. type TRealMat    = specialize TGenMat <real>;
  25. type TComplexMat = specialize TGenMat <complex>;
  26.  
  27. var MR: TRealMat;
  28.     MC: TComplexMat;
  29.     x: real;
  30.     c: complex;
  31. begin
  32.   x := MR.test;
  33.   c := MC.test;
  34. end.
  35.  

Akira1364

  • Hero Member
  • *****
  • Posts: 561
Re: Generics without specialization cannot be used as a type for a variable
« Reply #11 on: February 24, 2019, 03:14:37 am »
It's not really odd at all, in fact.

The key thing to note here is: your add operator overload is overloading a type for which the fields are themselves generic (as in, "unknown until specialized.") So it works, because there is no specific underlying implementation of "+", of course.

The math functions such as Abs, Sqr, Sqrt, Sin, and so on are however not generic in any way. They take specific, named types (as they should, of course.) So the compilation fails, because "T" is not a single, or an Int64, or anything else at all. It's literally nothing at that time.

It is true, though, that FPC is a bit inconsistent in how it handles generics. It is as far as I'm aware not supposed to care about the validity of generics *at all* until they're specialized, but it does, which introduces some limitations such as what you've encountered here.

So not necessarily a bug if you ask me, but at the very least a specific flaw in the current implementation of generics. Perhaps Sven might be able to weigh in with their opinion on this?

On a related note: Remember to always, always, always, specify that record parameters are either "const" or "var" in methods (in 99% of cases.) Circumstances where you ever actually want to have a "bare", unprefixed record parameter are extremely rare. Also simple math methods like what you're doing there should generally be marked as inline (as FPC will not inline them, ever, otherwise.)

(And yes, all of that stuff does specifically matter, folks. There are very specific, known things that FPC simply won't do, and isn't supposed to, with regards to optimization unless you tell it to.)
« Last Edit: February 25, 2019, 01:34:04 am by Akira1364 »

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Generics without specialization cannot be used as a type for a variable
« Reply #12 on: February 24, 2019, 02:38:31 pm »
It is true, though, that FPC is a bit inconsistent in how it handles generics. It is as far as I'm aware not supposed to care about the validity of generics *at all* until they're specialized, but it does, which introduces some limitations such as what you've encountered here.

Depends.  The C++ style of templates just records, and does all checking at specialization. The .NET style is mostly declarative.This was done to be able to easier reuse specializations for "close" other specializations, it is easier to determine if they are compatible from the declaration only.

The original FPC generics are more C++ style, the Delphi ones are more .NET style.

Nitorami

  • Sr. Member
  • ****
  • Posts: 481
Re: Generics without specialization cannot be used as a type for a variable
« Reply #13 on: February 24, 2019, 03:19:00 pm »
Quote
The key thing to note here is: your add operator overload is overloading a type for which the fields are themselves generic (as in, "unknown until specialized.") So it works, because there is no specific underlying implementation of "+", of course.

The math functions such as Abs, Sqr, Sqrt, Sin, and so on are however not generic in any way. They take specific, named types (as they should, of course.) So the compilation fails, because "T" is not a single, or an Int64, or anything else at all. It's literally nothing at that time.

@Akira1364: I still don't quite see the difference. The compiler has in fact many implementations for "+", for integer, single, double etc. and I just add another one. Or even more, I can declare as many overloaded "+" operators as I like, e.g. for booleans, and the compiler will still pick the correct one.

But this does not work for functions. The math functions were maybe a poor example, but let's define a new function addme (const a,b: real): real; and overload it with another version for integer, and the compiler will be unable to decide which one to use.

Is that intentional, maybe for the sake of type checking, or is it a lack of implementation ?




jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Generics without specialization cannot be used as a type for a variable
« Reply #14 on: February 24, 2019, 03:44:56 pm »
what makes you think using generics reduces duplicated code?
The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018