Sign in Join
Wiki copytemporary

Generics proposals: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Already implemented!
22
33> **Note:** Generic classes are already implemented in the 2.3.1 version of FPC. See the [FPC reference](http://www.freepascal.org/docs-html/ref/ref.html).
44
55This page contains proposals and ideas, which have lead to the current implementation and for further features.
66
77**Note, April 8th 2019: almost everything on this page is highly outdated and does not reflect the current state of FPC features.**
88
99## Why
1010
1111- Typesafety !
1212- Speed
1313- Readability
1414
1515See "Already existing stuff" for possible but lacking ways right now.
1616
1717## Interested Parties
1818
1919\- Almindor - dannym - fpk - giantm - oliebol - plugwash - Synopsis - tom - PublicJoke - neli - dr_evil
2020
2121## Usage example (uncategorized)
2222
2323TODO: combine the below stuff into one example using the \<\> syntax
2424
2525(tom proposal)
2626
2727``` pascal
2828 type
2929 // explicit addition of "generic" keyword. Maybe something like "uses generic <...>"
3030 // better use the <>-brackets because otherwise it may cause confusion with arrays
3131 // in the actual instantiation
3232 abc = class(..) generic <a, b>
3333 end;
3434 var
3535 // because they're still unused ;)
3636 xyz : abc<String, Integer>;
3737 xyz2 : abc<AObj1, AObj2>;
3838 begin
3939 // don't repeat generics in instance creation (if not necessary)
4040 xyz := abc.Create;
4141 xyz2 := abc<descendant_of_Aobj1, descendant_of_Aobj2>.Create;
4242 end.
4343```
4444
4545(dannym proposal new)
4646
4747``` pascal
4848 type
4949 GList = generic class of T
5050 public
5151 procedure Add(const stuff: T);
5252 end;
5353
5454 procedure GList.Add(const stuff: T); // note that here no 'of T'
5555 begin
5656 ...
5757 end;
5858
5959 var
6060 intlist: GList of Integer;
6161 begin
6262 intlist := GList.Create; // note that here the detail type is missing and
6363 // needs to be deducted from the var declaration
6464 //or intlist := typeof(intlist).Create;
6565 //intlist := GList of Integer.Create; <-- this is unclear if that works
6666 end;
6767```
6868
6969(dannym proposal - old)
7070
7171``` pascal
7272 type
7373 GList<T> = class
7474 public
7575 procedure Add(const stuff: T);
7676 end;
7777
7878 procedure GList<T>.Add(const stuff: T);
7979 begin
8080 ...
8181 end;
8282
8383 var
8484 intlist: GList<Integer>;
8585 begin
8686 intlist := GList<Integer>.Create;
8787 end;
8888```
8989
9090plugwash:
9191
9292``` pascal
9393 tmycollection=template(tobject)[x]
9494 tintegercollection=tmycollection[integer]
9595```
9696
9797oliebol:
9898
9999``` pascal
100100 template blaat<t:integer>(a:t);
101101 var x : t;
102102 begin
103103 do something with a, and use t with it
104104 end;
105105```
106106
107107note that we should have a generics section (like interface and implementation sections) containing the generics:
108108
109109``` pascal
110110 unit xyz;
111111 generics
112112 type
113113 GList......
114114 procedure GList.Add(const value: T);
115115 ....
116116 end.
117117```
118118
119119(eiffel)
120120
121121<http://paste.lisp.org/display/6236>
122122
123123(PublicJoke proposal)
124124
125125``` pascal
126126 unit Unit1(TMathType);
127127
128128 interface
129129
130130 type
131131 TMathClass = class
132132 public
133133 class function Add(a1, a2: TMathType): TMathType;
134134 end;
135135
136136 implementation
137137
138138 class function TMathClass.Add(a1, a2: TMathType): TMathType;
139139 begin
140140 Result:=a1+a2;
141141 end;
142142
143143 end.
144144```
145145
146146``` pascal
147147 unit Unit2;
148148
149149 type
150150 TMathType = Longword;
151151
152152 interface
153153
154154 implementation
155155
156156 end.
157157```
158158
159159``` pascal
160160 program prog;
161161
162162 uses
163163 Unit2,
164164 Unit1(Byte) as ByteMath in 'Unit1.pas',
165165 Unit1(TMathType) as PolyMath in 'Unit1.pas';
166166
167167 type
168168 TByteMathClass = ByteMath.TMathClass;
169169 TPolyMathClass = PolyMath.TMathClass;
170170
171171 var
172172 b: Byte;
173173 p: TMathType;
174174
175175 begin
176176 b:=TByteMathClass.Add(1, 2);
177177 p:=TPolyMathClass.Add(3, 4);
178178 end.
179179```
180180
181181(Gadnio proposal) I personally think the C++ way of doing this is clear enough AND will be the easiest to parse/understand:
182182
183183``` pascal
184184 template <T>
185185 TGenericObject< T > = record
186186 Data : T;
187187 OtherStuff : Pointer;
188188 end;{TGenericObject}
189189 template <TKey, TData = TGenericObject< boolean > >
190190 TGenericList = class( TWhee< TKey > )
191191 public
192192 function Lookup( const Key : TKey ): TData;
193193 template <THihi>
194194 class function DataToHihi( Data : TData ): THihi;
195195 end;{TGenericList}
196196```
197197
198198Also, note that template specialization is a very, very important thing (for me especially). In C++ they've got vector\< bool \> that behaves different than the standard vector\< T \>. An example:
199199
200200``` pascal
201201 template <T, TT>
202202 TSame = class
203203 class function Same: Boolean;
204204 end;{TSame}
205205 template <T>
206206 TSame = class
207207 class function Same: Boolean;
208208 end;{TSame}
209209```
210210
211211``` pascal
212212 template <T, TT>
213213 class function TSame.Same: Boolean;
214214 begin
215215 Result := false;
216216 end;{TSame<T, TT>.Same}
217217 template <T>
218218 class function TSame.Same: Boolean;
219219 begin
220220 Result := true;
221221 end;{TSame<T>.Same}
222222```
223223
224224This allows to make a compile-time check for same types that are not TObject, e.g.
225225
226226``` pascal
227227 TFoo = class;
228228 TBar = class( TFoo );
229229 var
230230 S : Boolean;
231231 begin
232232 S := TSame< String, Integer >.Same; //s = false
233233 S := TSame< Int64, Integer >.Same; //s = false
234234 S := TSame< String, String >.Same; //s = true
235235 S := TSame< TBar, TFoo >.Same;//s = false (not the same as "var x: TFoo; begin x := TBar.Create; end;" )
236236 end;
237237```
238238
239239Another thing to note: It would be very good if something like this (c++ syntax) would be implemented:
240240
241241``` pascal
242242 template <bool b>
243243 class h
244244 {
245245 public:
246246 static const int i = 0;
247247 };//h
248248```
249249
250250``` c
251251 template <>
252252 class h< true >
253253 {
254254 public:
255255 static const int i = 1;
256256 };
257257```
258258
259259``` c
260260 template <>
261261 class h< false >
262262 {
263263 public:
264264 static const int i = 2;
265265 };
266266```
267267
268268Here
269269
270270``` c
271271 h<false>.i = 2
272272 h<true>.i = 1
273273```
274274
275275ALWAYS.
276276
277277## Usage example
278278
279279If you add more syntax examples, please add equivalent to both. Two main syntaxes seem to be popular, "\<\>" which looks more or less like C++, c#, etc.. and adding a "generic" keyword which is more along the Pascal style of code. Go to [[Generics Vote]] to vote for one or the other syntax.
280280
281281### Using brackets: \<\>
282282
283283``` pascal
284284 type
285285 TGenericCollection<T: TCollectionItem> = class(TComponent)
286286 ...implement TCollection and use T
287287 end;
288288
289289 TCollection = TGenericCollection<TCollectionItem>;
290290 TFieldDefs = TGenericCollection<TFieldDef>;
291291
292292 TGenericList<T: PtrInt> = class(TObject)
293293 ...implement TList and use PtrInt size for code generation
294294 end;
295295
296296 TList = TGenericList<Pointer>;
297297```
298298
299299Implementation of TGenericCollection can be compiled as if (T: TCollectionItem) were used.
300300
301301Would this solve the circular dependency ? It seems so to me, because one knows at least as much as in current implementation of these classes, but I'm no compiler engineer.
302302
303303For procedures:
304304
305305``` pascal
306306 function MyFunc<T: TTreeItem>(A: T): T;
307307 begin
308308 // do something with the tree item
309309 end;
310310
311311 function Max<T>(A, B: T): T;
312312 begin
313313 if A < B then
314314 Result := B
315315 else
316316 Result := A;
317317 end;
318318```
319319
320320My restrictions won't allow implementing generic Max and Min, I guess. That really needs macro-alike stuff (for the compiler implementation).
321321
322322Another example for linked lists: (ripped from mail to fpc-devel of dannym, but modified a bit)
323323
324324``` pascal
325325 type
326326 TListItem<T> = record
327327 Data: T;
328328 Next: TListItem;
329329 end;
330330 PListItem = ^TListItem;
331331
332332 TList<T> = class
333333 private
334334 fHead: PListItem<T>;
335335 fTail: PListItem<T>;
336336 published
337337 procedure Add(Item: T);
338338 end;
339339
340340 procedure TList<T>.Add(Item: T);
341341 var
342342 node: PListItem<T>;
343343 begin
344344 New(node);
345345 node^.Data := Item;
346346 node^.Next := nil;
347347
348348 if Assigned(fTail) then begin
349349 fTail^.Next := node;
350350 end else begin
351351 fHead := node;
352352 end;
353353
354354 fTail := node;
355355 end;
356356
357357 type
358358 TApple = class
359359 end;
360360 TOrange = class
361361 end;
362362
363363 TAppleList = TList<TApple>;
364364
365365 var
366366 apples: TAppleList;
367367 apple: TApple;
368368 begin
369369 apples.Add(TApple.Create); // works
370370 apples.Add(TOrange.Create); // compile error
371371
372372 apple := apples[0]; // works
373373 apple := apples[1]; // not applicable
374374 apple := apples[0] as TApple; // works, but unneccessary
375375 apple := apples[1] as TApple; // not applicable
376376 end;
377377```
378378
379379### Using the "generic" keyword
380380
381381``` pascal
382382 type
383383 TGenericCollection = generic(T: TCollectionItem) class(TComponent)
384384 ...implement TCollection and use T
385385 end;
386386
387387 TCollection = specialize TGenericCollection(TCollectionItem);
388388 TFieldDefs = specialize TGenericCollection(TFieldDef);
389389
390390 TGenericList = generic(T: PtrInt) class(TObject)
391391 ...implement TList and use PtrInt size for code generation
392392 end;
393393
394394 TList = specialize TGenericList(Pointer);
395395```
396396
397397For procedures:
398398
399399``` pascal
400400 function generic(T: TTreeItem) MyFunc(A: T): T;
401401 begin
402402 // do something with the tree item
403403 end;
404404
405405 function generic(T) Max(A, B: T): T;
406406 begin
407407 if A < B then
408408 Result := B
409409 else
410410 Result := A;
411411 end;
412412```
413413
414414Another example for linked lists: (ripped from mail to fpc-devel of dannym, but modified a bit)
415415
416416``` pascal
417417 type
418418 TListItem = generic(T) record
419419 Data: T;
420420 Next: TListItem;
421421 end;
422422 PListItem = ^TListItem;
423423
424424 TList = generic(T) class
425425 private
426426 fHead: specialize PListItem(T);
427427 fTail: specialize PListItem(T);
428428 published
429429 procedure Add(Item: T);
430430 end;
431431
432432 procedure generic(T) TList.Add(Item: T);
433433 var
434434 node: specialize PListItem(T);
435435 begin
436436 New(node);
437437 node^.Data := Item;
438438 node^.Next := nil;
439439
440440 if Assigned(fTail) then begin
441441 fTail^.Next := node;
442442 end else begin
443443 fHead := node;
444444 end;
445445
446446 fTail := node;
447447 end;
448448
449449 type
450450 TApple = class
451451 end;
452452 TOrange = class
453453 end;
454454
455455 TAppleList = specialize TList(TApple);
456456
457457 var
458458 apples: TAppleList;
459459 apple: TApple;
460460 begin
461461 apples.Add(TApple.Create); // works
462462 apples.Add(TOrange.Create); // compile error
463463
464464 apple := apples[0]; // works
465465 apple := apples[1]; // not applicable
466466 apple := apples[0] as TApple; // works, but unneccessary
467467 apple := apples[1] as TApple; // not applicable
468468 end;
469469```
470470
471471Ugly things in need to be resolved:
472472
473473``` pascal
474474 { a generic type "TPointer" which is a pointer to type instantiated with }
475475 type TPointer = generic(Type) ^Type;
476476 { a generic type that instantiates all the enums for Type }
477477 type TAllEnums = generic(Type)(low(Type),high(Type));
478478```
479479
480480So, before a keyword can be real "nice" the following things need addressing:
481481
4824821. Double brackets ()(), looks C-ish again
4834832. In math, one usually defines functions with the parameters on the left of the equal sign, not on the right
484484
485485#### Suggestion 2
486486
487487Inspired by idea from oliebol in combination with ugly examples, in list of const, var, type sections, add "template" section:
488488
489489``` pascal
490490 const
491491 ThisIsAConst = 1;
492492
493493 type
494494 TMyEnum = (ab, ac);
495495
496496 template(T: TCollectionItem)
497497 TGenericCollection = class(TComponent)
498498 ...implement TCollection and use T
499499 end;
500500
501501 type
502502 TCollection = specialize TGenericCollection(TCollectionItem);
503503 TFieldDefs = specialize TGenericCollection(TFieldDef);
504504
505505 template(T: PtrInt)
506506 TGenericList = class(TObject)
507507 ...implement TList and use PtrInt size for code generation
508508 end;
509509
510510 type
511511 TList = specialize TGenericList(Pointer);
512512```
513513
514514For procedures:
515515
516516``` pascal
517517 function generic(T: TTreeItem) MyFunc(A: T): T;
518518 begin
519519 // do something with the tree item
520520 end;
521521
522522 function generic(T) Max(A, B: T): T;
523523 begin
524524 if A < B then
525525 Result := B
526526 else
527527 Result := A;
528528 end;
529529```
530530
531531Another example for linked lists: (ripped from mail to fpc-devel of dannym, but modified a bit)
532532
533533``` pascal
534534 template(T)
535535 TListItem = record
536536 Data: T;
537537 Next: TListItem;
538538 end;
539539 PListItem = ^specialize TListItem(T);
540540
541541 TList = class
542542 type
543543 PItem = specialize PListItem(T);
544544 private
545545 fHead: PItem;
546546 fTail: PItem;
547547 published
548548 procedure Add(Item: T);
549549 end;
550550
551551 ...implementation...
552552
553553 procedure generic(T) TList.Add(Item: T);
554554 var
555555 node: PItem;
556556 begin
557557 New(node);
558558 node^.Data := Item;
559559 node^.Next := nil;
560560
561561 if Assigned(fTail) then begin
562562 fTail^.Next := node;
563563 end else begin
564564 fHead := node;
565565 end;
566566
567567 fTail := node;
568568 end;
569569
570570 type
571571 TApple = class
572572 end;
573573 TOrange = class
574574 end;
575575
576576 TAppleList = specialize TList(TApple);
577577
578578 var
579579 apples: TAppleList;
580580 apple: TApple;
581581 begin
582582 apples.Add(TApple.Create); // works
583583 apples.Add(TOrange.Create); // compile error
584584
585585 apple := apples[0]; // works
586586 apple := apples[1]; // not applicable
587587 apple := apples[0] as TApple; // works, but unneccessary
588588 apple := apples[1] as TApple; // not applicable
589589 end;
590590```
591591
592592#### Suggestion 3
593593
594594``` pascal
595595 { Chu Jetcheng, 2008-07-24 }
596596
597597 program Generics;
598598
599599 type
600600 { The compiler should maintain a global table storing each specialized type for each generic. The
601601 `of' operator is used to create a specialization for a specified generic type. Specialized types
602602 of the same `type layout' (no other words) point to the same entry in the table of specializations
603603 of the specialized generic type, and thus are compatible. Generic objects and records should also
604604 be allowed. }
605605
606606 TGeneric = generic class(TAncestorClass) of T1, T2
607607 private
608608 FData1: T1;
609609 FData2: T2;
610610 public
611611 property Data1: T1 read FData1 write FData1;
612612 property Data2: T2 read FData2 write FData2;
613613 constructor Create(const A: T1; const B: T2);
614614 procedure WriteData; virtual;
615615 end;
616616
617617 TGenericDescendent = generic class(TGeneric of Integer, T3) of T3
618618 private
619619 FData3: T3;
620620 FObjectOfSameType: TGenericDescendent of T3;
621621 public
622622 property Data3: T3 read FData3 write FData3;
623623 property ObjectOfSameType: TGenericDescendent of T3 read FObjectOfSameType write FObjectOfSameType;
624624 constructor Create(const C: T3);
625625 procedure WriteData; override;
626626 end;
627627
628628 TGenericIR = TGeneric of Integer, Real;
629629 TGenericIRClass = class of TGenericIR;
630630
631631 TGenericISClass = class of (TGeneric of Integer, string);
632632
633633 PGenericObj = generic ^TGenericObj;
634634 TGenericObj = generic object of T
635635 MyField: T;
636636 procedure WriteData;
637637 end;
638638
639639 { TGeneric }
640640
641641 { Use the same syntax for method implementations as with immediate classes. }
642642
643643 constructor TGeneric.Create(const A: T1; const B: T2);
644644 begin
645645 FData1 := A;
646646 FData2 := B;
647647 end;
648648
649649 procedure TGeneric.WriteData;
650650 begin
651651 Writeln(FData1);
652652 Writeln(FData2);
653653 end;
654654
655655 { TGenericDescendent }
656656
657657 constructor TGenericDescendent.Create(const C: T3);
658658 begin
659659 inherited Create(16, C);
660660 FData3 := C;
661661 end;
662662
663663 procedure TGenericDescendent.WriteData;
664664 begin
665665 inherited;
666666 Writeln(FData3);
667667 if Assigned(FObjectOfSameType) then FObjectOfSameType.WriteData;
668668 end;
669669
670670 { TGenericObj }
671671
672672 procedure TGenericObj.WriteData;
673673 begin
674674 Writeln(MyField);
675675 end;
676676
677677 var
678678 Obj1: TGeneric of Integer, Real;
679679 Obj2, Obj3: TGenericDescendent of string;
680680 OldStyleObj: TGenericObj of Integer;
681681 OldStyleObjPtr: PGenericObj of Integer;
682682
683683 begin
684684 Obj1 := TGeneric.Create(32, 3.14);
685685 Obj1.WriteData;
686686 Writeln(#9, Obj1 is TGenericIR);
687687 Writeln(#9, Obj1 is TGenericIS);
688688
689689 Obj2 := TGenericDescendent.Create('Generic test.');
690690 Obj2.WriteData;
691691 Writeln(#9, Obj2 is TGenericIR);
692692 Writeln(#9, Obj2 is TGenericIS);
693693
694694 Obj3 := TGenericDescendent.Create('Another generic test.');
695695 Obj3.ObjectOfSameType := Obj2;
696696 Obj3.WriteData;
697697
698698 Obj3.Free;
699699 Obj2.Free;
700700 Obj1.Free;
701701
702702 OldStyleObj.MyField := 100;
703703 OldStyleObj.WriteData;
704704
705705 OldStyleObjPtr := New(PGenericObj of Integer);
706706 with OldStyleObjPtr^ do begin
707707 MyField := 202;
708708 WriteData;
709709 end;
710710 Dispose(ObjStyleObjPtr);
711711 end.
712712```
713713
714714## Terms
715715
716716type user:
717717
718718- the function in the compiler that defines a variable or return type and thus 'uses a (already defined) type'.
719719
720720immediate type:
721721
722722- not generic and not specialized, i.e. 'normal type'
723723
724724generic type:
725725
726726- template for a class with some unspecified types, never to be filled in into this class type. Only by specialisation.
727727
728728specialization:
729729
730730- copy generic type class to new specialized type class
731731
732732type parameters:
733733
734734- T and Q, the unknown types for the generic TSomeGeneric\<T,Q\>
735735
736736specialized type:
737737
738738- a generic type with all type parameters know, written into a real class type (tree) and reused if possible
739739
740740## Changes
741741
742742### Changes in class/record definition representation
743743
744744Each class definition representation has added fields for:
745745
746-` - class instantiation mode`\
747-`    0  immediate type`\
748-`    1  generic type, no instantiation, just generate specialized type`\
749-`    2  specialized type`
746+``` pascal
747+ - class instantiation mode
748+ 0 immediate type
749+ 1 generic type, no instantiation, just generate specialized type
750+ 2 specialized type
751+```
750752
751-` - when mode 2:`\
752-`     generic type uplink (XXXX, what, unitname, typename?)`\
753-`   when mode 1:`\
754-`     list of specialized types known so far (and where),`\
755-`     XXXX list items`\
756-`   when mode 0: `\
757-`     nothing`
753+``` pascal
754+ - when mode 2:
755+ generic type uplink (XXXX, what, unitname, typename?)
756+ when mode 1:
757+ list of specialized types known so far (and where),
758+ XXXX list items
759+ when mode 0:
760+ nothing
761+```
758762
759763### Changes in 'type user' compilation (for class/record types only)
760764
761765Each class type user will have to check mode of the class type.
762766
763767For the used class type, If mode is immediate
764768
765-` - proceed as always till now.`
769+```text
770+ - proceed as always till now.
771+```
766772
767773If mode is specialized
768774
769-` - proceed as always till now `\
770-` - keep in mind the generic type for some checks later (and debugging).`
775+``` pascal
776+ - proceed as always till now
777+ - keep in mind the generic type for some checks later (and debugging).
778+```
771779
772780If mode is generic, this:
773781
774-` - check 'list of specialized types' for the type parameter to use.`\
775-`   If there is already a specialized type, use that.`\
776-`   (given that it is compatible with 'compile parameters' XXXX)`
782+``` pascal
783+ - check 'list of specialized types' for the type parameter to use.
784+ If there is already a specialized type, use that.
785+ (given that it is compatible with 'compile parameters' XXXX)
786+```
777787
778-` - If not available, clone the generic type in the tree, and fill in the`\
779-`   type params with the actual types to use.`\
780-`   Generate machine code as normal.`\
781-`   Remember the new specialized type for later in the list by the `\
782-`   generic type and type params used.`
788+``` pascal
789+ - If not available, clone the generic type in the tree, and fill in the
790+ type params with the actual types to use.
791+ Generate machine code as normal.
792+ Remember the new specialized type for later in the list by the
793+ generic type and type params used.
794+```
783795
784796(the generic type is best written to some ppu as parse tree so that it can be refetched for cloning somewhen. To keep it easy, initially it should be limited to having its .pas file around when wanting to use a generic)
785797
786798### type params
787799
788800Generics store a list of the type params (names).
789801
790-` ('T', 'Q')`
802+```text
803+ ('T', 'Q')
804+```
791805
792806And Specializations store a list of type mappings from the real types to the type params, in the way of
793807
794808``` pascal
795809 type
796810 T = Integer;
797811 Q = Boolean;
798812```
799813
800814of course these are local to this class/record specialization.
801815
802816## Other things to note (for *later*)
803817
804818### What if a method implementation depends on the type used to know how the implementation should look like ?
805819
806820``` pascal
807821 procedure GList<T is TObject>.Add(const value: T);
808822 procedure GList<T is Integer>.Add(const value: T);
809823```
810824
811825?
812826
813827or more cryptic like
814828
815829``` pascal
816830 procedure GList<TObject>.Add(const value: T);
817831 procedure GList<Integer>.Add(const value: T);
818832```
819833
820834?
821835
822836or like
823837
824838``` pascal
825839 procedure GList<T>.Add(const value: Integer);
826840 procedure GList<T>.Add(const value: TObject);
827841```
828842
829843the latter would need extra compiler support but would be nicer, overall. Potentially clashing with normal overloads though.
830844
831845or as an alternative like:
832846
833847``` pascal
834848 {$IF T is TObject}
835849 procedure GList.Add(const value:TObject);
836850 {$ELSE}
837851 procedure GList.Add(const value:Integer);
838852 {$ENDIF}
839853```
840854
841855This would also allow to define constants or records in dependency of the generic type. This could be useful for example if you can specialize a class with static or dynamic array types (like array of double) and you need a static or dynamic array of integer having the same length as the generic. Currently we can play a bit with sizeOf(T), but this has its limitations (see <http://www.lazarusforum.de/viewtopic.php?f=10&t=11915#p106921>)...
842856
843857### How to do internal storage classes
844858
845859``` pascal
846860 type
847861 PNode<T> = ^TNode<T>;
848862 TNode<T> = record
849863 prev,next: PNode<T>;
850864 data: T;
851865 end;
852866
853867 type
854868 GList<T> = class
855869 private
856870 head, tail: PNode<T>;
857871 cnt: Integer;
858872 public
859873 procedure Add(const stuff: T);
860874 end;
861875```
862876
863877?
864878
865879This implies that generics are best implemented both for records and for classes alike! Also implies that pointer types need to support generic too.
866880
867881Also one specialization should chain other specializations (probably also when deriving from a generic).
868882
869883### How to derive from the generic type
870884
871885It would be good if generic types could be derived from.
872886
873887``` pascal
874888 type
875889 TGenericEventyList<T> = class(TGenericList<T>)
876890 public
877891 procedure Add(value: T); override;
878892 end;
879893 ...
880894```
881895
882896### Interfaces should support generics too
883897
884898``` pascal
885899 type
886900 IBa<T> = interface
887901 procedure Add(value: T);
888902 end;
889903 TBa<T> = class(...,IBla<T>,IInterface)
890904 end;
891905```
892906
893907(what to do about the guids of the specializations? probably just generate whatever, they dont have any real meaning after all)
894908
895909Of course this has limitations to check, like
896910
897911- the generic interface cannot be used, just its specializations
898912- the specialized interface cannot be used in a generic class interface list
899913- (the generic interface can be used in a generic class interface list)
900914- the generic interface itself is invalid to use anywhere (just as generic classes are) except for specializing and deriving from
901915- normal interfaces cannot derive from generic interfaces
902916- (generic interfaces can derive from generic interfaces) - given the type params are the same
903917- specialized interfaces cannot be derived from
904918
905919### Limitation of possible specialisations
906920
907921``` pascal
908922 type
909923 TGenericList<T: TObject or Integer> = ...
910924 end;
911925```
912926
913927Probably useful...
914928
915929### What to do with class functions
916930
917931I dont know a whole lot of how class functions are stored internally. What happens to class functions in generics?
918932
919933### 'class of'
920934
921935\<Almindor\> don't forget class of, we need class of compatibility for dynamic class factories
922936
923937\<Almindor\> There are possible problems with 'class of'
924938
925939## Implementation details
926940
927941### Sanity checks
928942
929943- Generic types cannot be instantiated from
930944- (Generic types can be derived from)
931945- Specialized types cannot be derived from
932946- Normal types cannot use generic types in its definition
933947- (Normal types can use specialized types in its definition)
934948- There are no half specialized types (one type param specified, the other not)
935949- prevent loops while 'unpacking' specializations (loop counter field in every specialization)
936950
937951### Things to note and not forgot
938952
939953- A class and record generic can use itself as type within its definition.
940954
941955example:
942956
943957``` pascal
944958 type
945959 TBla<T> = class
946960 parent: TBla<T>;
947961 end;
948962```
949963
950964- A class and record generic can use other generics as types within its definition.
951965
952966``` pascal
953967 type
954968 TBla<T> = class
955969 parent: TBlo<T>;
956970 end;
957971```
958972
959973## Implementation steps
960974
961975### Extend parser to support '\<T\>' and '\<T: bla or bla or bla\>' and '\<T,Q\>' notation
962976
963977Easy to do. Done.
964978
965979## Different ways to do it, one excluding the other
966980
967981### Way 1
968982
969983#### Change class/record/object/pointertype/interface parser to mark if it is a generic, a specialization or normal
970984
971985Mediate to do. Have done it for class/object and pointertype. Needs all kinds of clone functions for the defs and syms. fpk says he has patches. waiting.
972986
973987#### insert dummy symbols (typesym + typedef) for the type params in the generic
974988
975989Cannot be inserted into the object because parsing of types within objects has been disabled to make that possible:
976990
977991``` pascal
978992 type
979993 Row = Cardinal;
980994 TBla = class
981995 Row: Integer;
982996 end;
983997```
984998
985999weird.. (function searchsym_type, is not possible to have type definitions in records, objects, parameters)
9861000
9871001#### parse implementation section methods *for the generic*
9881002
9891003Is harder than it sounds, because the class/object the method is member of has to be searched for the type symbol for the type params (and these *not* derefed but just used as is).
9901004
9911005#### when parsing generics, use a dummy type in place of the type params
9921006
9931007For that, defer the type checking of the compiler in that case to 'as late as possible' or 'never for the generic' \<--- TODO... hard...
9941008
9951009#### Change derivation handlers to do the sanity checks
9961010
9971011Should pose no problems other than finding all the places.
9981012
9991013#### Add error messages for it
10001014
10011015#### Change pexpr (tnode creating)
10021016
10031017\<Synopsis+\> see the do_resulttypepass() calls in pexpr.pas But the compiler needs to known for example if it allows a . after the type
10041018
10051019- add a new tnode type for postfix operators (instead of resolving the operators into the fields/methods they reference) and defer type checking until later (just before binary generation - which is not done for the generic type).
10061020
10071021Examples for postfix operators are:
10081022
10091023- '\[\]' array member access (normal array, class default array property?)
10101024
10111025- '.' record/class member method/variable access
10121026
10131027- '^' pointer dereference
10141028
10151029- (automatic pointer dereference)
10161030
10171031\<Synopsis\> grep for m_auto and you'll find the places where it is used \<Synopsis\> only in pexpr.pas
10181032
10191033#### Change ppu streaming routines to save the tree of generics to the ppu
10201034
10211035#### Change type user to instantiate specialized types for generics when used
10221036
10231037Somewhat hard to do. (main work here :))
10241038
10251039For the instantiation
10261040
10271041- there is a dummy type sym needed (ok),
10281042- a new objectdef
10291043- clone of all the syms and defs (at least proc and property) of the original class/object
10301044- and replacing all the type params by the correct types.
10311045- Store that into the module as def (not on disk).
10321046- (\<Synopsis+\> an additional symtable shall be added to tmodule: tempsymtable; all temporary tdef's shall be stored in that symtable)
10331047- Then resume compiling for the class/object.
10341048
10351049### Way 2
10361050
10371051just keep the source code of the generic around and do blind replacing of the type params and then compile a specialization from that as 'source'
10381052
10391053#### Notes
10401054
10411055\<Synopsis+\> but the code to generate the specialization at runtime can be generated at the time the specialization is created. It doesn't need to be stored in ppu. all information is available to generate the tree at that time.
10421056
10431057\<Synopsis+\> In the 2.1.x development series a rewrite of the ppu loading is planned to make it more clean and more maintainable. Currently it is risky to fix bugs without introducing new bugs
10441058
10451059\<plugwash-\> It needs to be stored in some form so that the specialisations can be generated from it. \<Synopsis+\> but you can't store them in the ppu. Or with the same limitations as inlining. No references to the implementation symtable.
10461060
10471061fpk has patches to make cloning defs possible.
10481062
1049106301:28:24 \<Synopsis+\> users will complain; things like:
10501064
10511065``` pascal
10521066 interface
10531067 generictype declar
10541068 implementation
10551069 procedure helper;
10561070 begin
10571071 end
10581072 constructor generictype.create
10591073 begin
10601074 helper
10611075 end
10621076 end.
10631077```
10641078
10651079Will not be allowed.
10661080
10671081Another problem:
10681082
10691083``` pascal
10701084 unit a;
10711085 interface
10721086 generictypeA
10731087 implementation
10741088 uses B
10751089 end.
10761090
10771091 unit b;
10781092 interface
10791093 uses a
10801094 implemenation
10811095 begin
10821096 generictypeA<integer>.craete
10831097 end.
10841098```
10851099
10861100At the time that the implementation of B is parsed it needs already the code of generictypeA. but that is not yet parsed! The compiler doesn't know anything yet. It only knows that generictypeA exists.
10871101
10881102\<fpk-\> dannym, Synopsis: we can simply forbid such code in generics which cause the problem
10891103
10901104\<fpk-\> i.e. everything used by generics must be already included in the interface uses clause ... or forbid references to units used only in the implementation section :)
10911105
10921106\<Synopsis+\> fpk had a good idea that can solve some of the issues: unit bla; interface ... generics ... implementation ... initialisation ... finalizatrion end.
10931107
10941108\<Synopsis+\> The generics delcarations shall be put in a separate section before implementation is parsed
10951109
10961110\<dannym\> at 'procedure TTest.Add(x: T);', T needs to be known. but it doesnt seem that the object symtable is searched for the class method parameters. correct ?
10971111
10981112\<Synopsis+\> dannym: correct, object symtables can not contain types, because that created problems. there is special code for that: remove the objectsymtable before parsing type declarations.
10991113
11001114``` pascal
11011115 { for records, don't search the recordsymtable for the symbols of the types }
11021116 oldsymtablestack:=symtablestack;
11031117 symtablestack:=symtablestack.next;
11041118```
11051119
11061120Because you can have a field with the same name as a type
11071121
11081122``` pascal
11091123 type
11101124 Wrd = Word;
11111125 R = Record
11121126 Wrd : Wrd;
11131127 end
11141128```
11151129
11161130this is allowed. To support this the record or object symtable is remvoed from the symtablestack when the type is parsed.
11171131
11181132## Already existing stuff
11191133
11201134### metaclasses
11211135
11221136``` pascal
11231137 type
11241138 TObjectClass = class of TObject;
11251139 TTypedList = class
11261140 private
11271141 Fwanttype: TObjectClass;
11281142 ...
11291143 public
11301144 constructor Create(itemtype: TObjectClass);
11311145 procedure Add(item: TObject);
11321146 ...
11331147 end;
11341148 constructor TTypedList.Create(itemtype: TObjectClass);
11351149 begin
11361150 Fwanttype := itemtype;
11371151 ...
11381152 end;
11391153 procedure TTypedList.Add(item: TObject);
11401154 begin
11411155 assert(item.Class = Fwanttype);
11421156 ...
11431157 end;
11441158
11451159 ...
11461160 TSomeStuff = class
11471161 end;
11481162 ...
11491163 Flist := TTypedList.Create(TSomeStuff);
11501164```
11511165
11521166Advantages
11531167
11541168- already works
11551169
11561170Disadvantages
11571171
11581172- slow at runtime
11591173- cannot do simple types
11601174- need to cast all the time even for the supported types (i.e. when reading). eeek.
11611175
11621176## To be incorporated here
11631177
11641178### Chat logs
11651179
11661180\<Synopsis\> <http://www.eleforum.com/cgi-bin/eleweb_lift?action=3&script=wall&num=18&reversesort=true&type=today&starttime=00:00&endtime=23:59&startdate=01-02-05&enddate=01-02-05> \<Synopsis\> or \<Synopsis\> <http://neli.hopto.org:3980/~micha/irc-fpc/2005/01/%23fpc.20050102.log>
11671181
11681182## See also
11691183
11701184- [[Generics]]
11711185
11721186---
11731187*From the Free Pascal wiki, page "Generics proposals", as it stood on 2020-02-16 after a change by GAN, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
11741188Category: FPC
11751189Category: Proposals

Back to history