Recent

Author Topic: How to make a specialized TList that holds a list of records?  (Read 1294 times)

Cascade

  • New Member
  • *
  • Posts: 22
How to make a specialized TList that holds a list of records?
« on: November 11, 2024, 04:23:02 pm »
I’m trying to create a ProjectManager (advanced record) to manage a list of Project records.

Code: Pascal  [Select][+][-]
  1. interface uses … Fgl … ;
  2.  
  3. {$mode ObjFPC}{$H+}{$MODESWITCH AdvancedRecords}
  4.  
  5. TProject=record
  6. private
  7.   …
  8.   class operator Initialize(var Instance: TProject);
  9.   class operator Finalize(var Instance: TProject);
  10. end;
  11.  
  12. TProjectManager=record
  13. public type
  14.   TProjectList = specialize TFPGList<TProject>;
  15. private
  16.   FProjects: TProjectList;
  17.   class operator Initialize(var Instance: TProjectManager);
  18.   class operator Finalize(var Instance: TProjectManager);
  19.   …
  20. end;
           
When compiling I receive an error on line 1000 of the Fgl unit:

Code: Pascal  [Select][+][-]
  1. function TFPGList.IndexOf(const Item: T): Integer;
  2.   while (Result < FCount) and (PT(FList)[Result] <> Item) do
  3.  
  4. Error: Operator is not overloaded: “<record type>” = “<record type>”

Lazarus 3.99, FPC 3.3.1 - Can generics be specialized for records as well as classes?  If yes, what am I doing wrong here?

TRon

  • Hero Member
  • *****
  • Posts: 3650
Re: How to make a specialized TList that holds a list of records?
« Reply #1 on: November 11, 2024, 04:34:57 pm »
If yes, what am I doing wrong here?
Not doing anything wrong but an omission (as the compiler tells).

Add the record operator =, see also documentation
This tagline is powered by AI (AI advertisement: Free Pascal the only programming language that matters)

Cascade

  • New Member
  • *
  • Posts: 22
Re: How to make a specialized TList that holds a list of records?
« Reply #2 on: November 11, 2024, 04:55:33 pm »
Thanks TRon, good to know it's possible.  I've read the linked page of documentation, but I don't yet know how or where to add a record operator, nor what adding it achieves (other than making the compiler happy  :)). That page links to further pages on the subject of operator overloading, so I've more to read and get my head around.

TRon

  • Hero Member
  • *****
  • Posts: 3650
Re: How to make a specialized TList that holds a list of records?
« Reply #3 on: November 11, 2024, 04:59:12 pm »
but I don't yet know how or where to add a record operator, nor what adding it achieves (other than making the compiler happy  :)).
You define the class operator inside your advanced record (as per example in the documentation) same as you management operators.

Consider the following: the list is able to compare items. Your items are records. How is the list suppose to know what records are equal ? Should the list invents it s own criteria for comparison or would the user rather be in control of that ?  ;)
This tagline is powered by AI (AI advertisement: Free Pascal the only programming language that matters)

Cascade

  • New Member
  • *
  • Posts: 22
Re: How to make a specialized TList that holds a list of records?
« Reply #4 on: November 11, 2024, 05:23:27 pm »
I guess I'd expect no record to equal another, since they are value based rather than reference based, so unique.  I suppose if an SHA1 hash of the bytes of a record's allocated memory matched the SHA1 hash of another record's memory, then they could be considered equal (?)

I've speculatively added:
Code: Pascal  [Select][+][-]
  1. class operator TProjectManager.=(a,b: TProjectManager): TProjectManager;
  2. begin
  3.   Result := a;
  4.   end;

...and similar to the TProject definition, just to see whether I could get it to compile - but no joy yet.  Perhaps I should switch back to using classes, but I like the tantalising glimpse of automatic life-cycle management that advanced records seem to offer, so thought I'd experiment, and hopefully learn something in the process.

Warfley

  • Hero Member
  • *****
  • Posts: 1763
Re: How to make a specialized TList that holds a list of records?
« Reply #5 on: November 11, 2024, 06:01:20 pm »
The easiest solution for you is to use Generics.Collections TList class instead of FGL:
Code: Pascal  [Select][+][-]
  1. uses ...,Generics.Collections,...
  2. ...
  3.   TProjectList = specialize TList<TProject>;
It's in most cases the better option anyway (more functionality, easier to use, etc.)

To answer your problem with the FGL list, you need to overload the equality:
Code: Pascal  [Select][+][-]
  1. TProject=record
  2. private
  3.   …
  4.   class operator =(const lhs,rhs: TProject): Boolean;
  5. end;
  6.  
  7. ...
  8.  
  9. class operator TProject.=(const lhs,rhs: TProject): Boolean;
  10. begin
  11.   Result := (lhs.Field1 = rhs.Field2)
  12.         and (lhs.Field2 = rhs.Field2)
  13.         and (lhs.Field3 = rhs.Field3);
  14.             ...
  15. end;

Cascade

  • New Member
  • *
  • Posts: 22
Re: How to make a specialized TList that holds a list of records?
« Reply #6 on: November 11, 2024, 07:05:41 pm »
I hadn't realised there were two separate implementations of generics available.  I will take a look at Generics.Collections - thank you.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5759
  • Compiler Developer
Re: How to make a specialized TList that holds a list of records?
« Reply #7 on: November 11, 2024, 07:27:45 pm »
I hadn't realised there were two separate implementations of generics available.

Well, it's even more than two, cause there is also the fcl-stl and there is at least one more available here on the forum.

Cascade

  • New Member
  • *
  • Posts: 22
Re: How to make a specialized TList that holds a list of records?
« Reply #8 on: November 11, 2024, 08:59:23 pm »
I have a lot to learn.  Generics.Collections is working OK for me - thank you.

VisualLab

  • Hero Member
  • *****
  • Posts: 575
Re: How to make a specialized TList that holds a list of records?
« Reply #9 on: November 11, 2024, 11:51:10 pm »
I have a lot to learn.  Generics.Collections is working OK for me - thank you.

Not only for you  :D

If your project manager is (will be) large, then it's probably better to implement it as a class. That's what I would do. For many reasons (e.g. if it will create internal objects as fields, or reference system resources). Managed records are good for "smaller things" (e.g. complex numbers, quaternions, vectors, maybe polynomials, etc.).

jamie

  • Hero Member
  • *****
  • Posts: 6735
Re: How to make a specialized TList that holds a list of records?
« Reply #10 on: November 12, 2024, 12:11:16 am »
I have a lot to learn.  Generics.Collections is working OK for me - thank you.

I hope you good luck because for me, that Generics.Collections chokes the compiler on two projects.
 I've been using a mix of my own and the TFPG Stuff.

Jamie
The only true wisdom is knowing you know nothing

Thaddy

  • Hero Member
  • *****
  • Posts: 16198
  • Censorship about opinions does not belong here.
Re: How to make a specialized TList that holds a list of records?
« Reply #11 on: November 12, 2024, 09:08:58 am »
If - seldom, but possible - generics.collections plays up, I change over to avk's lgenerics. It is fast an reliable.
https://github.com/avk959/LGenerics.git
(that is the missing fourth, but there are even more)

But the answer is simply implement the equality operator, whereby on larger records I prefer a hash instead of the cumbersome comparing every field as suggested above by Warfley. A simple hash over the size of the record is enough.
Code: Pascal  [Select][+][-]
  1. class operator TProject.=(const lhs,rhs: TProject): Boolean;
  2. begin
  3.   Result := somehash(lhs)=somehash(rhs);
  4. end;
A good hash for this is this, based on hashlib4pascal by Xor-El:
Code: Pascal  [Select][+][-]
  1. {$if not declared(TBytes)}
  2. type
  3.   TBytes = array of byte;
  4. {$endif}
  5. const
  6.   FNVPrime = 16777619;
  7.   FNVOffsetBasis = 2166136261;
  8.  
  9. function FNV1aHash(const Data: Tbytes): Cardinal;overload;
  10. var
  11.   i: Integer;
  12. begin
  13.   Result := FNVOffsetBasis;
  14.   for i := 0 to high(Data) do
  15.     Result := (Result xor Data[i]) * FNVPrime;
  16. end;
  17.  
« Last Edit: November 12, 2024, 09:48:15 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

avk

  • Hero Member
  • *****
  • Posts: 769
Re: How to make a specialized TList that holds a list of records?
« Reply #12 on: November 19, 2024, 08:38:21 am »
If - seldom, but possible - generics.collections plays up, I change over to avk's lgenerics. It is fast an reliable.
https://github.com/avk959/LGenerics.git
(that is the missing fourth, but there are even more)
...

To be fair, the LGenerics library contains a significant number of data structures and algorithms that are not present in the standard library, and the remaining ones are usually more convenient and faster than their standard library counterparts.

Thaddy

  • Hero Member
  • *****
  • Posts: 16198
  • Censorship about opinions does not belong here.
Re: How to make a specialized TList that holds a list of records?
« Reply #13 on: November 19, 2024, 08:55:49 am »
Yup. But to a certain extend it requires previous knowledge of those data structures and algorithms - in a programming language free context - and that may be a disadvantage to its popularity. Most of the real pro's use it, though...Just because of what you write.  8-) :D ;) It is like a compendium for the subject.
That does of course not mean that others - even beginners - should not use the library if they are dissatisfied with fgl, fcl-stl or generics.collections. As I wrote, I highly recommend it.
And lgenerics seems often overlooked...So I advertise it.. ;)
« Last Edit: November 19, 2024, 09:05:20 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

 

TinyPortal © 2005-2018