Generics for Dummies

Für Fragen von Einsteigern und Programmieranfängern...
Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
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

Beitrag von af0815 »

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 :-)
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Generics for Dummies

Beitrag von fliegermichl »

Ich drücke mich immer noch :-)

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
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

Beitrag von af0815 »

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).

winx_club_fairy
Beiträge: 1
Registriert: Di 19. Apr 2022, 13:37

Re: Generics for Dummies

Beitrag von winx_club_fairy »

Eine gute Idee hier, Ich werde mich der Gruppe anschließen :wink:

Benutzeravatar
theo
Beiträge: 10497
Registriert: Mo 11. Sep 2006, 19:01

Re: Generics for Dummies

Beitrag von theo »

Bei dem Beispiel mit

Code: Alles auswählen

generic function Add
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?

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
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

Beitrag von af0815 »

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).

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Generics for Dummies

Beitrag von fliegermichl »

theo hat geschrieben:
Do 21. Apr 2022, 10:22
Bei dem Beispiel mit

Code: Alles auswählen

generic function Add
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?
Zumal 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;

Benutzeravatar
theo
Beiträge: 10497
Registriert: Mo 11. Sep 2006, 19:01

Re: Generics for Dummies

Beitrag von theo »

af0815 hat geschrieben:
Do 21. Apr 2022, 11:01
Theo, ich würde nur gerne wissen von was dort speziell gesprochen wird. Deswegen der Titel des Threads.
Um auf deinen Titel zu antworten: Dummies brauchen keine Generics! :lol:
(...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.

Warf
Beiträge: 1909
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Generics for Dummies

Beitrag von Warf »

theo hat geschrieben:
Do 21. Apr 2022, 10:22
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?
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;
Nicht nur das die generics funktion viel kürzer und lesbarer ist, man hat hier auch compiler type checking:

Code: Alles auswählen

  WriteLn(Sum([1, 2, 3, 4])); // kompiliert
  WriteLn(Sum(['A', 3, 5])); // fehler
Während die beiden Funktionen bei der Variant variante beide geschluckt werden und erst beim ausführen einen Fehler werfen.

Aber zum beispiel eine Funktion wie Format kann man einfach nicht mit Generics bauen, da dies laufzeitpolymorphismus voraussetzt
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.
Willst du wirklich argumentieren das in dem beispiel oben die Variant funktion besser lesbar ist als die Generische?

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.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
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

Beitrag von af0815 »

fliegermichl hat geschrieben:
Do 21. Apr 2022, 11:13
Zumal 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;
Nur das ich den Code in der Implementierung nicht mehrfach habe. Soweit habe ich das bereits verstanden.

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).

PascalDragon
Beiträge: 829
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

Beitrag von PascalDragon »

theo hat geschrieben:
Do 21. Apr 2022, 10:22
Bei dem Beispiel mit

Code: Alles auswählen

generic function Add
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?
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.

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>;
(der Typ TArray<> ist in der System-Unit deklariert als generic TArray<T> = array of T)
fliegermichl hat geschrieben:
Do 21. Apr 2022, 11:13
theo hat geschrieben:
Do 21. Apr 2022, 10:22
Bei dem Beispiel mit

Code: Alles auswählen

generic function Add
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?
Zumal 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;
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?
af0815 hat geschrieben:
Do 21. Apr 2022, 11:01
Theo, ich würde nur gerne wissen von was dort speziell gesprochen wird. Deswegen der Titel des Threads.
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

Warf
Beiträge: 1909
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Generics for Dummies

Beitrag von Warf »

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:

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;
7 Funktionen die alle exakt das gleiche machen, und sich nur im typen unterscheiden. Mit generics kann man das als eine funktion machen

Code: Alles auswählen

generic function max<T>(const A, B: T): T;
begin
  if A > B then Result := A
  else Result := B;
end;
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:

Code: Alles auswählen

var
 a, b, c: MPInteger; // Multi Precision Integer aus der GMP unit;
begin
  ...
  c := max(a, b);
end;
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:

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
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

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);
Oder woran ich aktuell arbeite, Iteratoren Bibliothek: https://github.com/Warfley/ObjPasUtils/ ... /iterators
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);
Was ich hoffe was wenn die bugs mit der Implicit specialization gefixed sind, am ende so aussieht:

Code: Alles auswählen

HexStr := Reduce(Map(Iterate(arr), ByteToHex), ConcatStr);
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:

Code: Alles auswählen

function AsyncConcat(AExecutor: TExecutor; AName: String; ANumber: Integer): String;
...
Line := Await<String>(AsyncFunction<String, String, Integer>(AsyncConcat, AName, i));
Was ich hoffe was mit implicit specialization irgendwann nur noch so aussieht:

Code: Alles auswählen

function AsyncConcat(AExecutor: TExecutor; AName: String; ANumber: Integer): String;
...
Line := Await(AsyncFunction(AsyncConcat, AName, i));
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:

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}

PascalDragon
Beiträge: 829
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

Beitrag von PascalDragon »

Warf hat geschrieben:
Do 21. Apr 2022, 16:36
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.
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. ;)
Warf hat geschrieben:
Do 21. Apr 2022, 16:36
Was ich hoffe was wenn die bugs mit der Implicit specialization gefixed sind, am ende so aussieht:
Ich nehme mal an, das was ich aktuell so an Bugs von dir (direkt oder indirekt) reinkriege, basiert auf deinen Bibliotheken? :mrgreen:
FPC Compiler Entwickler

Warf
Beiträge: 1909
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Generics for Dummies

Beitrag von Warf »

PascalDragon hat geschrieben:
Do 21. Apr 2022, 18:27
Ich nehme mal an, das was ich aktuell so an Bugs von dir (direkt oder indirekt) reinkriege, basiert auf deinen Bibliotheken? :mrgreen:
Ich hab einfach mal überall die specialization rausgehauen und geschaut wos kracht
Am wochenende schau ich nochmal genauer, dann werden vielleicht noch ein paar aufkommen :D

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Generics for Dummies

Beitrag von fliegermichl »

Warf hat geschrieben:
Do 21. Apr 2022, 16:36
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:
...
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.

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 :-)

Antworten