Generics for Dummies
- af0815
- Lazarusforum e. V.
- Beiträge: 6216
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Generics for Dummies
Ich glaube der Titel sagt alles.
Nachdem im internationalen Forum wieder etwas angekündigt wurde
Feature announcement: Implicit generic function specializations sollte ich mich doch einmal mit Generics auseiandersetzen.
Gibt es da für Anfänger verständliche Erklärungen ?!
Ja, ja ich habe mich um das Thema lange herumgedrückt
Nachdem im internationalen Forum wieder etwas angekündigt wurde
Feature announcement: Implicit generic function specializations sollte ich mich doch einmal mit Generics auseiandersetzen.
Gibt es da für Anfänger verständliche Erklärungen ?!
Ja, ja ich habe mich um das Thema lange herumgedrückt
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
- fliegermichl
- Lazarusforum e. V.
- Beiträge: 1436
- Registriert: Do 9. Jun 2011, 09:42
- OS, Lazarus, FPC: Lazarus Fixes FPC Stable
- CPU-Target: 32/64Bit
- Wohnort: Echzell
Re: Generics for Dummies
Ich drücke mich immer noch
- af0815
- Lazarusforum e. V.
- Beiträge: 6216
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Re: Generics for Dummies
Dann werden wir mal hier mit einer Selbsthilfegruppe starten
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
-
- Beiträge: 1
- Registriert: Di 19. Apr 2022, 13:37
Re: Generics for Dummies
Eine gute Idee hier, Ich werde mich der Gruppe anschließen
Re: Generics for Dummies
Bei dem Beispiel mit
bin ich mir nicht sicher, ob das noch im Sinne des Erfinders (von Pascal) ist.
Eigentlich könnte man das auch mit Variant Parametern lösen, aber ist das noch "schön" und braucht man das?
Code: Alles auswählen
generic function Add
Eigentlich könnte man das auch mit Variant Parametern lösen, aber ist das noch "schön" und braucht man das?
- af0815
- Lazarusforum e. V.
- Beiträge: 6216
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Re: Generics for Dummies
Theo, ich würde nur gerne wissen von was dort speziell gesprochen wird. Deswegen der Titel des Threads.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
- fliegermichl
- Lazarusforum e. V.
- Beiträge: 1436
- Registriert: Do 9. Jun 2011, 09:42
- OS, Lazarus, FPC: Lazarus Fixes FPC Stable
- CPU-Target: 32/64Bit
- Wohnort: Echzell
Re: Generics for Dummies
Zumal man das ja auch mit Überladenen Funktionen lösen kann.theo hat geschrieben: ↑Do 21. Apr 2022, 10:22Bei dem Beispiel mitbin ich mir nicht sicher, ob das noch im Sinne des Erfinders (von Pascal) ist.Code: Alles auswählen
generic function Add
Eigentlich könnte man das auch mit Variant Parametern lösen, aber ist das noch "schön" und braucht man das?
Code: Alles auswählen
function Add(s1, s2 : string) : string; overload;
function Add(i1, i2 : Integer) : integer; overload;
Re: Generics for Dummies
Um auf deinen Titel zu antworten: Dummies brauchen keine Generics!
(...und andere Leute vermutlich auch nicht)
Aus meiner Sicht ist das Thema eher von "akademischem Interesse" und der praktische Nutzen begrenzt.
Vielleicht ist es der Lesbarkeit des Codes sogar abträglich.
Mich interessiert das nicht besonders, aber es gibt bestimmt andere Meinungen.
-
- Beiträge: 1912
- Registriert: Di 23. Sep 2014, 17:46
- OS, Lazarus, FPC: Win10 | Linux
- CPU-Target: x86_64
Re: Generics for Dummies
Generics sind anders als variante parameter, denn Generics sind Kompilezeit Polymorphismus, während Variante Parameter Laufzeitpolymorphismus ermöglichen.
Damit sind Generics grundsätzlich effizienter, da die gesammte Polymorphismus auflösung während dem Kompilieren stattfindet, und weniger Fehleranfällig, da der Compiler Typchecks machen kann. Dafür sind sie weniger Flexibel.
Z.B.
Code: Alles auswählen
// Variants
function Sum(arr: array of const): Variant;
var
i: Integer;
begin
for i:=Low(arr) to High(arr) do
if i = 0 then case arr[i].VType of
vtInteger: Result := arr[i].VInteger;
vtAnsiString: Result := arr[i].VAnsiString;
...
end
else if TVarData(Result).vtype <> arr[i].VType then
raise Exception.Create('Types must match to form a sum') // Runtime polymorphism requires runtime type checking
else case arr[i].VType of
vtInteger: Result := Integer(Result) + arr[i].VInteger;
vtAnsiString: Result := String(Result) + String(arr[i].VVariant);
...
end;
end;
// Generics
generic function Sum<T>(arr: array of T): T;
var
i: Integer;
begin
for i := Low(arr) to High(arr) do
if i = Low(arr) then Result := arr[i]
else Result := Result + arr[i];
end;
Code: Alles auswählen
WriteLn(Sum([1, 2, 3, 4])); // kompiliert
WriteLn(Sum(['A', 3, 5])); // fehler
Aber zum beispiel eine Funktion wie Format kann man einfach nicht mit Generics bauen, da dies laufzeitpolymorphismus voraussetzt
Willst du wirklich argumentieren das in dem beispiel oben die Variant funktion besser lesbar ist als die Generische?Aus meiner Sicht ist das Thema eher von "akademischem Interesse" und der praktische Nutzen begrenzt.
Vielleicht ist es der Lesbarkeit des Codes sogar abträglich.
Mich interessiert das nicht besonders, aber es gibt bestimmt andere Meinungen.
Generics sind grundsätzlich das selbe wie überladen, nur mit dem extra bonus das du keine doppelte tipparbeit hast. Sieh die sum funktion oben, die funktioniert für Integer, Singles, Doubles, Strings, etc. und das in nur 5 zeilen
Zuletzt geändert von Warf am Do 21. Apr 2022, 13:31, insgesamt 3-mal geändert.
- af0815
- Lazarusforum e. V.
- Beiträge: 6216
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Re: Generics for Dummies
Nur das ich den Code in der Implementierung nicht mehrfach habe. Soweit habe ich das bereits verstanden.fliegermichl hat geschrieben: ↑Do 21. Apr 2022, 11:13Zumal man das ja auch mit Überladenen Funktionen lösen kann.Code: Alles auswählen
function Add(s1, s2 : string) : string; overload; function Add(i1, i2 : Integer) : integer; overload;
Warf hat in der zwischen zeit was Gutes aufs Tapet gebracht. Der Vergleich ist recht anschaulich.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
-
- Beiträge: 834
- Registriert: Mi 3. Jun 2020, 07:18
- OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
- CPU-Target: Aarch64 bis Z80 ;)
- Wohnort: München
Re: Generics for Dummies
Wie Warf bereits geschrieben hat, schlägt sich die Verwendung von Variant auf die Laufzeitperformance nieder (der Compiler kann unter Umständen zum Beispiel sogar besser optimieren oder ganze Codebereiche weglassen) von der Typsicherheit mal ganz abgesehen. Das Add ist jetzt nicht das Beste Beispiel, aber was ich zum Beispiel gar nicht mehr missen möchte, sind generische Listen: wenn ich eine Liste für LongInt-Werte haben möchte, dann mache ich einfach type TMyLongIntList = specialize TFPGList<LongInt> (aus der Unit fgl) oder type TMyLongIntList = specialize TList<LongInt> (aus der Unit Generics.Collections) an statt mir Contnrs.TList abzuleiten und dann typsichere Methoden hinzuzufügen. Letzteres muss ich für jeden Typen wiederholen, den ich in ner Liste haben möchte, wenn mir Typsicherheit (aber auch einfache Benutzbarkeit) was bedeutet.theo hat geschrieben: ↑Do 21. Apr 2022, 10:22Bei dem Beispiel mitbin ich mir nicht sicher, ob das noch im Sinne des Erfinders (von Pascal) ist.Code: Alles auswählen
generic function Add
Eigentlich könnte man das auch mit Variant Parametern lösen, aber ist das noch "schön" und braucht man das?
Oder wenn man mal wieder feststellt, dass man für ein dynamisches Array als Parameter oder Funktionsergebnis einen separaten Typ deklarieren muss:
Code: Alles auswählen
function WhatEver(aArg: specialize TArray<LongInt>): specialize TArray<LongInt>;
Und für jeden weiteren Typ implementierst du die Funktion erneut, obwohl all verwendeten Typen das + unterstützen und du das mit Generics also einfach eleganter und mit weniger Tipparbeit machen könntest? Oder würdest du am Ende sogar Include-Dateien und Makros nutzen, um weniger Tipparbeit zu haben?fliegermichl hat geschrieben: ↑Do 21. Apr 2022, 11:13Zumal man das ja auch mit Überladenen Funktionen lösen kann.theo hat geschrieben: ↑Do 21. Apr 2022, 10:22Bei dem Beispiel mitbin ich mir nicht sicher, ob das noch im Sinne des Erfinders (von Pascal) ist.Code: Alles auswählen
generic function Add
Eigentlich könnte man das auch mit Variant Parametern lösen, aber ist das noch "schön" und braucht man das?Code: Alles auswählen
function Add(s1, s2 : string) : string; overload; function Add(i1, i2 : Integer) : integer; overload;
Es geht bei dem Feature hauptsächlich um weniger Tipparbeit.
Generics an sich können nicht einfach so verwendet werden. Wenn du also generic TMyType<T> = record SomeField: T; end oder generic function Foobar<S, T>(aArg: S): T deklarierst, dann kannst du woanders diese nicht einfach als TMyType oder Foobar(…) nutzen, sondern du musst sie „spezialisieren”, also den Typparametern (hier T und S) einen fixen Typ zuweisen. In den nicht-Delphi Modi geht das per specialize TMyType<LongInt> und specialize Foobar<LongInt>(…) für LongInt oder specialize TMyType<String> und specialize Foobar<String>(…) für String (in den Delphi-Modi entsprechend einfach ohne das specialize). Das specialize TMyType<LongInt> ist quasi erst der finale Typbezeichner bzw. specialize Foobar<LongInt> der finale Funktionsbezeichner.
Bei Typbezeichnern ist es nun so, dass der Compiler nicht ahnen kann, was du haben möchtest, bei Funktionsbezeichnern kann der Compiler jedoch unter Umständen aus den verwendeten Parametern für den Aufruf eben die generischen Typparameter selbst herausfinden und damit den generischen Funktionsbezeichner von selbst (also „implizit”) spezialisieren. Wenn du also Add(SomeLongInt, SomeOtherLongInt) für generic function Add<T>(aArg1, aArg2: T): T hast, dann sieht der Compiler, okay, der erste Parameter könnte vom Typ LongInt sein. Also fixieren er T mal auf LongInt und schaut, ob dann weiter alles gut geht. Der zweite Parameter ist freundlicherweise auch eine Variable vom Typ LongInt also passt das was für T genommen wurde und der Compiler kann das implizit als specialize Add<LongInt>(SomeLongInt, SomeOtherLongInt) spezialisieren.
Der Compiler hat also das was er möchte - einen spezialisierten Bezeichner - und der Benutzer hat kürzeren, knackigeren Code.
In diesem Fall ist das neue Feature tatsächlich nur Syntactic Sugar für ein Feature, das bereits existiert (nämlich generische Funktionen und Methoden, welche mit 3.2.0 eingeführt wurden) und würde Delphi das nicht ebenfalls unterstützen hätten wir es wohl gar nicht erst eingeführt, da wir reine Syntactic Sugar Features in nahezu allen Fällen vermeiden.
FPC Compiler Entwickler
-
- Beiträge: 1912
- Registriert: Di 23. Sep 2014, 17:46
- OS, Lazarus, FPC: Win10 | Linux
- CPU-Target: x86_64
Re: Generics for Dummies
Aber um mal eine kurze Erklärung für Generics zu geben, Generic erlauben es Typen und Funktionen zu erstellen die im nachhinein angepasst werden können. So als würde man den Code schreiben aber geziehlt lücken lassen die dann zu einem späteren Zeitpunkt ausgefüllt werden können.
Man erstellt also so zu sagen eine Blaupause von einem Typen oder einer Funktion die dann auf verschiedene Fälle angewendet werden kann. Im Grunde ist also die Idee, wenn man sehr oft code doppelt schreibt, indem sich nur ein paar sachen ändern schreibt man lieber ein Generic und lässt die verschiedenen sachen dann als Parameter übergeben.
Zum beispiel die Unit Math hat die Folgenden funktionen:
7 Funktionen die alle exakt das gleiche machen, und sich nur im typen unterscheiden. Mit generics kann man das als eine funktion machen
Das deckt alle 7 funktionen von oben ab, und noch dazu erlaubt die erweiterung auf alle weiteren Typen die den > operator unterstützen. Z.B:
Das heist das Generics sich vor allem eignen in Bibliotheken die viel wiederverwendeten code haben.
Generische Parameter können aber nicht nur für Typen von Parametern verwendet werden, z.B. können diese auch direkt referenziert werden:
Als kurze erklärung, TCompare wird hier nicht als Typ für irgendeine Variable verwendet, sondern lediglich als funktion. Soweit ich weis unterstützt der FPC keine direkten Funktionen als Generische Parameter, deshalb muss der Umweg über den typen mit der class function gemacht werden. Mit diesen funktionen kann man jeden Typen der < und > implementiert sortieren.
In bestehendem code wie z.B. TStringLists sortierung, wird das über Events also Funktionspointer geregelt. Allerdings bedeutet das das das zur Laufzeit geregelt werden muss. Das heist zum einen das das in dem Funktionspointer Falsche daten Drin stehen können (z.B. Nil) dann krachts, was bei Generics der Compiler nicht erlaubt, zum anderen kann der Compiler hier optimieren, sodass am ende da wirklich nix anderes als < oder > steht.
Da allerdings alles zur Compiletime passiert, sind keinerlei Runtime informationen vorhanden. D.h. auch das die Typen nicht direkt verwand sind. So ist z.B. TList<Integer> und TList<Float> 2 verschiedene typen, und man kann nicht z.b. das eine in das andere Casten oder ähnliches.
Ein paar Beispiele, wie z.b. ich in der letzten Zeit Generics verwendet habe:
Dynamische Typen, z.B. ein Union typ: https://github.com/Warfley/ObjPasUtils/ ... namictypes
Oder woran ich aktuell arbeite, Iteratoren Bibliothek: https://github.com/Warfley/ObjPasUtils/ ... /iterators
Z.B. um einen Byte Array als Hex String darzustellen
Was ich hoffe was wenn die bugs mit der Implicit specialization gefixed sind, am ende so aussieht:
Oder bei meiner STAX bibliothek: https://github.com/Warfley/STAX
Gibt es Tasks die rückgabewerte haben können, das ist in dieser form eigentlich nur wirklich mit Generics möglich, und somit z.B. ermöglicht einen Funktionsaufruf im hintergrund zu starten und wenn die Funktion fertig ist aufgeweckt zu werden und das ergebnis zurückzubekommen:
Was ich hoffe was mit implicit specialization irgendwann nur noch so aussieht:
Aber auch als parameter sind Generics sehr nützlich, z.B. habe ich eine Pfadbibliothek gebaut: https://github.com/Warfley/ObjPasUtils/ ... rc/pathlib
Und da Pfade ja auf verschiedenen betriebsystemen unterschiedlich sind, werden die über Generics Parametrisiert:
Man erstellt also so zu sagen eine Blaupause von einem Typen oder einer Funktion die dann auf verschiedene Fälle angewendet werden kann. Im Grunde ist also die Idee, wenn man sehr oft code doppelt schreibt, indem sich nur ein paar sachen ändern schreibt man lieber ein Generic und lässt die verschiedenen sachen dann als Parameter übergeben.
Zum beispiel die Unit Math hat die Folgenden funktionen:
Code: Alles auswählen
function Max(a, b: Integer): Integer;inline; overload;
function Max(a, b: Cardinal): Cardinal; overload;
function Max(a, b: Int64): Int64;inline; overload;
function Max(a, b: QWord): QWord;inline; overload;
function Max(a, b: Single): Single;inline; overload;
function Max(a, b: Double): Double;inline; overload;
function Max(a, b: Extended): Extended;inline; overload;
Code: Alles auswählen
generic function max<T>(const A, B: T): T;
begin
if A > B then Result := A
else Result := B;
end;
Code: Alles auswählen
var
a, b, c: MPInteger; // Multi Precision Integer aus der GMP unit;
begin
...
c := max(a, b);
end;
Generische Parameter können aber nicht nur für Typen von Parametern verwendet werden, z.B. können diese auch direkt referenziert werden:
Code: Alles auswählen
type
generic TLess<T> = class
class function Compare(const A, B: T): Boolean; inline; static; // Gibt A < B zurück
end;
generic TGreater<T> = class
class function Compare(const A, B: T): Boolean; inline; static; // Gibt A < B zurück
end;
generic procedure Swap<T>(var A, B: T); inline
var
tmp: T;
begin
tmp := A;
A := B;
B := tmp;
end;
generic function Sort<T, TCompare>(Data: specialize TArray<T>): specialize TArray<T>;
var i, j: Integer;
begin
Result := Data;
SetLength(Result, Length(Data)); // in Result kopieren
// Bubblesort weil faul:
for i:=0 to Length(Result) -1 do
for j:=0 to Length(Result) - 2 - i do
if TCompare.Compare(Result[j+1], Result[j]) then
Swap(Result[j], Result[j+1]);
end;
// benutzung:
Sort<Integer, specialize TLess<Integer>(data); // Aufsteigend sortieren
Sort<Integer, specialize TGreater<Integer>(data); // Absteigend sortieren
In bestehendem code wie z.B. TStringLists sortierung, wird das über Events also Funktionspointer geregelt. Allerdings bedeutet das das das zur Laufzeit geregelt werden muss. Das heist zum einen das das in dem Funktionspointer Falsche daten Drin stehen können (z.B. Nil) dann krachts, was bei Generics der Compiler nicht erlaubt, zum anderen kann der Compiler hier optimieren, sodass am ende da wirklich nix anderes als < oder > steht.
Da allerdings alles zur Compiletime passiert, sind keinerlei Runtime informationen vorhanden. D.h. auch das die Typen nicht direkt verwand sind. So ist z.B. TList<Integer> und TList<Float> 2 verschiedene typen, und man kann nicht z.b. das eine in das andere Casten oder ähnliches.
Ein paar Beispiele, wie z.b. ich in der letzten Zeit Generics verwendet habe:
Dynamische Typen, z.B. ein Union typ: https://github.com/Warfley/ObjPasUtils/ ... namictypes
Code: Alles auswählen
var
union: TUnion<String, Integer>;
begin
union := EmptyUnion; // Empty
union := 'Hello World'; // Set the value to being a string
union := 42; // Set the value to being an integer
...
if union.isFirst then
WriteLn('Union is String: ', Union.First);
if union.isSecond then
WriteLn('Union is Integer: ', Union.Second);
Z.B. um einen Byte Array als Hex String darzustellen
Code: Alles auswählen
HexStr := Reduce<String>(Map<Byte, String>(Iterate<Byte>(arr), ByteToHex), ConcatStr);
Code: Alles auswählen
HexStr := Reduce(Map(Iterate(arr), ByteToHex), ConcatStr);
Gibt es Tasks die rückgabewerte haben können, das ist in dieser form eigentlich nur wirklich mit Generics möglich, und somit z.B. ermöglicht einen Funktionsaufruf im hintergrund zu starten und wenn die Funktion fertig ist aufgeweckt zu werden und das ergebnis zurückzubekommen:
Code: Alles auswählen
function AsyncConcat(AExecutor: TExecutor; AName: String; ANumber: Integer): String;
...
Line := Await<String>(AsyncFunction<String, String, Integer>(AsyncConcat, AName, i));
Code: Alles auswählen
function AsyncConcat(AExecutor: TExecutor; AName: String; ANumber: Integer): String;
...
Line := Await(AsyncFunction(AsyncConcat, AName, i));
Und da Pfade ja auf verschiedenen betriebsystemen unterschiedlich sind, werden die über Generics Parametrisiert:
Code: Alles auswählen
TWindowsPathParams = record
const PathDelim = '\';
const CaseSensitive: Boolean = False;
class function isAbsolute(const Path: String): Boolean; static; inline;
class function isRoot(const Path: String): Boolean; static; inline;
end;
TUnixPathParams = record
const PathDelim = '/';
const CaseSensitive: Boolean = True;
class function isAbsolute(const Path: String): Boolean; static; inline;
class function isRoot(const Path: String): Boolean; static; inline;
end;
TWindowsPath = specialize TCustomPath<TWindowsPathParams>;
TUnixPath = specialize TCustomPath<TUnixPathParams>;
{$IfDef WINDOWS}
TPath = TWindowsPath;
{$Else}
TPath = TUnixPath;
{$EndIf}
-
- Beiträge: 834
- Registriert: Mi 3. Jun 2020, 07:18
- OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
- CPU-Target: Aarch64 bis Z80 ;)
- Wohnort: München
Re: Generics for Dummies
Richtig, das steht noch immer auf meiner Liste. Hoffentlich kann ich das (zusammen mit ein paar weiteren Generics Bugs) demnächst angehen, wenn ich ein, zwei weitere große Brocken von meiner Liste runter habe.
Ich nehme mal an, das was ich aktuell so an Bugs von dir (direkt oder indirekt) reinkriege, basiert auf deinen Bibliotheken?
FPC Compiler Entwickler
-
- Beiträge: 1912
- Registriert: Di 23. Sep 2014, 17:46
- OS, Lazarus, FPC: Win10 | Linux
- CPU-Target: x86_64
Re: Generics for Dummies
Ich hab einfach mal überall die specialization rausgehauen und geschaut wos krachtPascalDragon hat geschrieben: ↑Do 21. Apr 2022, 18:27Ich nehme mal an, das was ich aktuell so an Bugs von dir (direkt oder indirekt) reinkriege, basiert auf deinen Bibliotheken?
Am wochenende schau ich nochmal genauer, dann werden vielleicht noch ein paar aufkommen
- fliegermichl
- Lazarusforum e. V.
- Beiträge: 1436
- Registriert: Do 9. Jun 2011, 09:42
- OS, Lazarus, FPC: Lazarus Fixes FPC Stable
- CPU-Target: 32/64Bit
- Wohnort: Echzell
Re: Generics for Dummies
Also das mit den 7 gleichen Funktionen, die im Prinzip alle das gleiche machen, kann ich noch gut folgen und diese generische Funktion spart Tipparbeit.Warf hat geschrieben: ↑Do 21. Apr 2022, 16:36Aber um mal eine kurze Erklärung für Generics zu geben, Generic erlauben es Typen und Funktionen zu erstellen die im nachhinein angepasst werden können. So als würde man den Code schreiben aber geziehlt lücken lassen die dann zu einem späteren Zeitpunkt ausgefüllt werden können.
Man erstellt also so zu sagen eine Blaupause von einem Typen oder einer Funktion die dann auf verschiedene Fälle angewendet werden kann. Im Grunde ist also die Idee, wenn man sehr oft code doppelt schreibt, indem sich nur ein paar sachen ändern schreibt man lieber ein Generic und lässt die verschiedenen sachen dann als Parameter übergeben.
Zum beispiel die Unit Math hat die Folgenden funktionen:
...
Das andere Beispiel mit der generischen Sortierung ist mir zu hoch oder ich muss es wohl noch 10 mal lesen um es begreifen zu können