Getter und Setter

English

In der objektorientierten Programmierung gilt es als fortschrittlich, getter und setter zu verwenden, statt auf Attribute direkt zuzugreifen, weil das einem die Flexibilität gibt, später auf berechnete Attribute umzuschwenken. Etwas hässlich ist das, weil die getter und setter, etwas willkürlich den Attributnamen mit so einem vorangestellten „get“ oder „is“ oder „set“ und eventueller Umwandlung der Groß- und Kleinschreibung einzelner Zeichen versehen. Eine subtile Besonderheit ist, dass es verwirrend wird, wenn Attributnamen mit „get“, „is“ oder „set“ beginnen. Gerade Boolean-Attribute ist man versucht mit „hasSomething“, „isSomething“, „doesSomething“, „canSomething“, „mustSomething“,… o.ä. zu benennen, was dann zu dem Getter „getIsSomething()“ oder „isIsSomething()“ führt. Oder man lässt in dem Fall das Präfix weg, aber nur beim Getter…

Schöner ist es, wenn man Getter und Setter natürlich bennen kann, wie das z.B. in C#, Ruby und Scala der Fall ist: Man schreibt den Getter so, als würde man das Attribut public machen und darauf zugreifen, aber hat durch die entsprechenden Sprachkonstrukte die Möglichkeit, die Getter und Setter durch andere Implementierungen zu ersetzen, wenn der Bedarf besteht. Es gibt sicher wichtigeres, aber das ist zumindest schöner, lesbarer und deshalb weniger fehleranfällig. Und sprachlich auch sauberer als diese „halb-magic“-Bedeutung von „get…“, „set…“ und „is…“.

Im Grunde genommen sind aber auch Zugriffe auf Listen und Maps oft eine Art Getter und Setter:
y=a.get(pos) könnte man auch als y=a[pos] schreiben wollen, entsprechend a.put(pos, x) auch als a[pos]=x. Dasselbe gilt für Maps mit u=m.get(k), was schöner und intuitiver als etwas in der Art von u=m[k] wäre. Oder statt m.put(k, v) so etwas wie m[k]=v. Aus genügend abstrakter Sicht ist das nicht so wichtig, aber wenn die Lesbarkeit sich verbessert, macht man weniger Fehler und so hat man pragmatisch gesehen einen kleinen Qualitäts und Effizienzgewinn mit der Zuweisungsschreibweise.

Nun sind aber Setter in Wirklichkeit oft problematisch. Es ist immer gut, Objekte immutable zu haben, weil man sie dann problemloser zwischen Threads herumreichen kann, ohne dass es zu Fehler bei gleichzeitigen Zugriffen kommen kann. Nun stellt sich aber die Frage, wie man dann das Objekt konstruieren soll. Ein Konstruktor mit positionalen Parametern ist zwar möglich, aber oft nicht sehr lesbar, wenn die Parameterliste nicht völlig überschaubar und klar ist. So etwas wie benannte Parameter könnte sehr viel helfen. Ein anderes Muster ist es, ein temporäres Objekt mit Settern aufzubauen und dann daraus das eigenteliche unveränderliche (immutable) Objekt zu generieren. Man kann dafür spezielle Setter nehmen, die jeweils das veränderte Objekt zurückgeben und das etwa so etwas schreiben wie
SomethingImmutable s = new SomethingTemp().setX(x).setY(y).setZ(z).toSomething(),
was nicht superschön ist, aber wenn man auf Java Wert legt, doch eine Möglichkeit.

Hier zeigt sich auch, warum es so schön ist, wenn man Listen und Maps und vielleicht andere Collections einfach mit allen Elementen konstruieren und dann gleich immutable machen kann. In Java geht das für Listen immerhin schon mit
Collections.immutableList(Arrays.asList(a, b, c, d, e, f, g, h))
machen. Wobei diese Konstruktion relativ neu ist und wegen der Konstruktionsphase Collections nicht defaultmäßig immutable sein können. Immerhin könnte man ein
Arrays.asImmutableList(T..t)
definieren. Oder eine Methode auf List
.immutable().
Schöner (klarer, lesbarer, weniger fehleranfällig) wäre es aber, wenn man das als
[a, b, c, d, e, f, g, h]
schreiben könnte. Für die Ausgabe von Listen mittels toString() wird so etwas ja schon verstanden. Für das Konstruieren von Maps gibt es in anderen Programmiersprachen auch Schreibweisen, die etwa so aussehen wie
m = { k1 => v1, k2 => v2, k3 => v3, k4 => v4}.
Will man sich normalerweise dafür interessieren, welche Map-Implementierung jetzt genommen wird? So etwas ließe sich als
m = new TreeMap{k1 => v1, k2 => v2, k3 => v3, k4 => v4}
schreiben. Solche Dinge waren für Java 8 vorgesehen, sind aber wohl in letzter Minute rausgeflogen oder auf Java 9 verschoben worden.

Share Button

Neue Projekte

Ab 1. September 2014 bin ich für neue Projekte verfügbar.

Share Button

Division mit Rest

Die Division mit Rest ist in vielen Programmiersprachen enthalten und man könnte meinen, dass klar ist, was damit gemeint ist. Meistens wird diese Restbildung mit „%“ geschrieben, was alle von C übernommen haben und was auch gut ist. Außer man will etwas mit Prozentrechnung programmieren und ist vom Taschenrechner für % etwas anderes gewohnt.

Aber man sollte etwas vorsichtig sein.

Zunächst gilt bei vielen Programmiersprachen die Regel, dass „immer“

    \[ a = ( a/b) * b + a \% b \]

gilt. Das ist schön, aber manche Programmiersprachen finden es logischer, wenn a/b eine Fließkommazahl oder eine rationale Zahl ergibt. Eine „ganzzahlige gerundete Division“ wäre natürlich zusätzlich cool, aber wenn man es so ausdrückt, fällt schon auf, wo die Schwierigkeit liegt. Setzen wir mal einen positiven Divisor voraus…. Wie wird hier gerundet? Je nach Rundungsverfahren ist für r = a \% b immer r \ge 0 (Ruby) oder nur r \ge 0 für a \ge 0 und für negative a ist es r \le 0 (Scala). Denkbar wäre aber auch, dass -\frac{a}{2} \le r < \frac{a}{2} gilt.
Leider ist es ein bisschen schwierig, eine elegante Schreibweise zu finden, um die Rundungsmethode für das % zu definieren.

In der Praxis benötigt man diese Reste oft für weitere Verarbeitungen. Was macht man nun mit den negativen Resten?
Oft ist es sinnvoll, a zu negativen Resten hinzu zu addieren. Dann hat man immer noch einen legitimen Rest, aber mit dem richtigen Vorzeichen, etwa das, was bei Ruby sowieso herauskäme.

Share Button

Millibytes

Wir lernen alle paar Monate oder mindestens alle paar Jahre neue Begriffe wie Kilobyte, Megabyte, Gigabyte, Terabyte, Petabyte und Exabyte kennen. Man muss immer aufpassen, weil damit die Potenzen von 1024 oder die von 1000 gemeint sein können. 1024 ist praktischer in der Informatik, aber 1000 ist praktischer für Anbieter von Festplatten, weil sie dann ein eindrucksvolleres Schild auf die Ware kleben können, ohne sehr unehrlich zu sein. Und bei Terabytes macht der Unterschied schon etwa 10% aus und es wird mehr wenn wir Exabytes oder Hepabytes oder Okabytes oder was auch immer man da erfinden wird, verwenden. Im Moment betrifft das Problem nur große Firmen wie Google und Google achtet bei der Einstellung von neuen Mitarbeitern sehr auf ein gewisses Mindestniveau, so dass sie nicht gefährdet sind, sich beim Festplattenhersteller wegen dieser 10-20% über den Tisch ziehen zu lassen.

Aber wie sieht es aus mit Millibytes? Das klingt komisch. Kleiner als ein Byte geht nicht, außer man überlegt etwas länger und kommt darauf, dass es noch Bits gibt, also Achtelbytes. Jedes Bit ist eine Antwort auf eine Frage, die nur mit Ja oder Nein beantwortet werden kann. Ein kleiner Nebenaspekt kommt mit Binärdaten ins Spiel. Diese sind üblicherweise als eine Sequenz von Bytes dargestellt. In Wirklichkeit sollte es eine Sequenz von Bits sein, das heißt, wir sollten genaugenommen beim letzten Byte wissen, welche Bits noch dazugehören und welche nur Füllbits sind, die wir ignorieren müssen. Das lässt sich durch entsprechende Binärformate in den Griff bekommen, wenn man angibt, wieviele Bits die Daten lang sind und dann die Bytes folgen.

Aber gibt es halbe Bits? Oder Millibits? So absurd ist die Idee nicht. Wenn wir eine Netzwerkverbindung haben, die absolut zuverlässig funktioniert, dann ist ein Bit das ankommt definitiv dasselbe wie das, was reingeschickt wurde. Es enthält also den Gegenwert von einem Bit Information darüber, was eingegeben wurde. Wenn der Kanal nun gar nicht funktioniert und zufällige Bits rauskommen, ist deren Informationsgehalt 0. Man erfährt gar nichts darüber, was gesendet wurde. Typische Verbindungen liegen irgendwo dazwischen. Das Bit was rauskommt, hat also etwas mit dem was reingesteckt wurde, zu tun, ist aber nicht 100% zuverlässig. Mit fehlerkorrigierenden Codes kann man die Zuverlässigkeit auf Kosten der Kapazität steigern. Analysiert man die ganze Situation, stellt man fest, dass ein physikalisch empfangenes Bit weniger als ein Bit Information bringt. Man braucht im Durchschnitt etwas mehr als ein übertragenes Bit, um ein Bit wirkliche Information von der Quelle wirklich mit einer gewünschten Zuverlässigkeit zu ermitteln. Die Zuverlässigkeit wird nie 100% erreichen, aber theoretisch 99.9999% mit so vielen Neunen wie man will. Am Ende des Tages bringt uns ein Bit, das physikalisch übertragen wird, also durchschnittlich nur 0.7, 0.95 oder 0.00012 Bits tatsächliche Information, weil es im Kontext der Fehlerkorrektur mit anderen Bits kombiniert werden muss. Und das sind dann 700, 950 oder 0.12 Millibits bzw. 87.5, 118.75 oder 0.015 Millibytes. Die Diskussion wegen 1024 und 1000 ist auch hier relevant, aber ich überlasse sie gerne den Lesern…

Share Button

Flashsort in Ruby

English

Es gibt auf github eine einfache Implementierung von Flashsort in Ruby, nachdem hier auf github schon eine Implementierung in C zu finden ist. Die C-Implementierung ist typischerweise schneller als die libc-Funktion qsort, aber letztlich hängt das von den Daten ab und davon, wie gut die metric-Funktion ist, die man zusätzlich zur Vergleichsfunktion bei Flashsort liefern muss. Man kann sich diese metric-Funktion als eine Art monotone Hashfunktion vorstellen, also gilt

    \[\bigwedge_{a,b: a\le b} m(a) \le m(b) \]

Diese zusätzlich benötigte Funktion oder Methode ist nicht wirklich vorhanden, außer bei numerischen Werten, was den Einsatz von Flashsort etwas erschwert. Entscheidend für eine gute Performance ist eine gute metric-Funktion, allerdings sind bei typischen Text-Dateien schon ziemlich triviale Implementierungen ganz brauchbar.

In diesem Blogbeitrag sind weitere Sortieralgorithmen für Ruby gezeigt.

Share Button

Advanced Akka

In der vergangenen Wochenende hat sich die Möglichkeit ergeben, an der Type-Safe-Schulung über „Advanced Akka“ teilzunehmen. Akka ist ein Framework zur Parallelisierung und Verteilung von Verabeitungsoperationen einer größeren Applikation, das auf Scala basiert. Akka ist selbst in Scala geschrieben, aber es wurde darauf geachtet, dass es auch mit Java benutzbar ist. Unabhängig von der konkreten Implementierung ist es aber auch konzeptionell interessant, weil Akka Ideen umsetzt, wie man massiv parallele Applikationen entwickeln kann. Erlang, LFE und Elixir verwenden zum Beispiel ähnliche Konzepte, vielleicht noch etwas radikaler, während Scala ja einen „sanften“ Übergang zur funktionalen Welt ermöglichen soll, ähnlich wie C++ für den Einstieg in die Objektorientierung von ein paar Jahren.

Im Fall, dass man Akka verteilt betreibt, sind natürlich wieder die Serialisierungsmechanismen interessant. Man sollte das berücksichtigen und zumindest nicht zu feingranulare Zugriffe über das Netzwerk verteilt durchführen, wenn die Parallelisierung eine hohe Performance bieten soll.

Share Button

Systemprogrammierung für Posix und Win32

Systemprogrammierung macht man in beiden Fällen mit C, da sollte es einige Ähnlichkeiten zwischen den Systemen geben und wenn man genau schaut, sieht man die auch. Aber auf den ersten Blick sehen die entsprechenden C-Programme sehr verschieden aus. Das überrascht nun wieder, da doch eigentlich mit der Programmiersprache C auch die libc standardisiert ist und das sogar einigermaßen eingehalten wird. Leider ist die libc gerade im betriebssystemnahen Bereich mehr oder weniger auf die Schnittmenge vieler Systeme zugeschnitten und stellt einiges an Funktionalität der darunter liegenden Betriebsysteme nicht zur Verfügung. So kann man vieles generisch programmieren, aber es gibt doch immer wieder Bereiche, in denen es vorteilhaft oder sogar fast zwingend notwendig ist, eine betriebssystemspezifische Implementierung zu wählen.

Rein optisch sieht man den Unterschied sofort. Die typischen Win32/Win64-Programme verwenden schon Funtionsnamen mit Camel-Case und großen Anfangsbuchstaben und bevorzugt sehr lange Namen für Funktionen, Variablen, Konstanten und Macros und sehr lange Parameterlisten. So werden die MS-Windowsprogramm lang, aber man kann wenigstens oft sehen, was in etwa gemeint ist. Letztlich muss man aber die Konzepte verstanden haben und Zugriff auf eine Funktions- und API-Referenz haben, ob das nun Man-Pages oder die Webseite einer großen Firma ist, ist sekundär. Eigentlich ist dieser Unterschied auch nicht so wichtig, weil man sich an andere Schreibweisen für die konzeptionell gleiche Sache schnell gewöhnen kann. Ärgerlich ist nur, dass man wirklich alles zweimal schreiben muss. In der Praxis wird man vielleicht die I/O- und IPC-Funktionalität, die die Software benötigt, an der man arbeitet, in einer oder mehreren Bibliotheken behandeln, wohlgemerkt spezifisch das, was man braucht und nicht eine generische Verallgemeinerung beider APIs, die nie fertig und nie richtig gut wird. Die restliche Programmlogik kann man vielleicht generisch gestalten.

Es gibt auch Möglichkeiten, unter MS-Windows mehr oder wenigeer viele der unter Posix (Linux, Unix, MacOSX, etc.) bekannten Funktionen unter MS-Windows zu benutzen. Mit cygwin bekommt man die meisten, das ist sozusagen so eine generische Schicht, aber auch die nativen Win32/Win64-Bibliothken enthalten oft über das geforderte Minimum hinaus Funktionen, die so typischerweise unter Posix üblich sind, z.B. open(), close(), read() und write().

Möglichkeiten zur Generierung von Nebenläufigkeit mit Prozessen und Threads sind in beiden Systemen vorhanden, aber auch hier mit subtilen semantischen Unterschieden, die es einem schwer machen können. Interprozesskommunikation haben beide Systeme und man kann natürlich auch recht vie l mit TCP/IP-lösen, was dann generisch ist.
Die bekannte Thematik mit „/“ vs. „\“ als Datei-Pfad-Trennzeichen kann man dagegen leicht lösen, indem man konsequent „/“ verwendet. Das verstehen die Win32- und Win64-API-Funktionen gut.

Die leidige Problematik mit den Zeilenwecheln mit ctrl-M ctrl-J vs nur Ctrl-J ist auch nicht so schlimm, wie man meint. Man kann eine Datei unter MS-Windows binär anlegen und dann wird das Ctrl-M nicht eingefügt. Und über weite Strecken werden beide Konventionen auf beiden Systemen problemlos verstanden und können entsprechend eingesetzt werden. Sinnvoll ist nur, dass man das definiert und sich auf einen Weg einigt.

Share Button

Statische und dynamische Typisierung

Es wird sehr viel „Informatik-Theologie“ um die Frage der statischen und dynamischen Typisierung betrieben. Da ich mir zu dieser Frage noch keine abschließende Meinung gebildet habe, kann ich hier nur ein paar Überlegungen zu der Thematik darlegen.

Eine Frage ist, wie statisch ist diese statische Typisierung eigentlich? Ein typischer Kandidat ist Java, das immer gerne als Musterbeispiel für statische Typisierung aufgeführt wird. Nun ist Java aber erst einmal sicher dynamisch typisiert. Jedes Java-Objekt hat seinen Typ dabei und dieser wird zur Laufzeit beachtet. Es gibt dann „ClassCastException“ oder so etwas. Man kommt recht weit ohne Casts. Früher brauchte man die für die Collections, aber das ist durch Generics ein Stück weit entschärft worden. Nun sind aber Generics in einigen Kombinationen dermaßen schwierig zu handhaben, man denke nur an die Kombination aus Collections und Arrays. Und wie viele kennen Kovarianz und Kontravarianz Manche Fälle werden dann in der Realität so gelöst, dass man die Generics wegcastet. Das ist Realität in den Java-Quelltexten dieser Welt, auch wenn man es noch so schlecht findet… Was bleibt ist die dynamische Typisierung und „etwas“ statische Typisierung. Es ist viel statische Typisierung, aber keineswegs durchgängig. Andere Programmiersprachen, wie z.B. F#, Scala oder Haskell haben die statische Typisierung konsequenter umgesetzt und dort funktioniert sie auch besser.

Man braucht natürlich einen Mechanismus, um die richtige Funktionalität für die Daten zu verwenden. Das kann durch eine Art von dynamischer Typisierung geschehen, im Prinzip für Polymorphie wichtig. Oder es kann auch programmatisch geschehen, indem quasi die richtige Funktionalität aufgerufen wird und das entsprechend schon zur Compilezeit so festgelegt wird. Das kann durch statische Typisierung unterstützt werden, aber es gibt natürlich auch andere Mechanismen.

Was statische Typisierung verspricht ist aber, dass man weniger Fehler macht. Man kann sagen, dass statische Typen eine Art „Constraints“ sind. Man sieht dadurch bestimmte Fehler, die man vermeiden kann. Die Freunde dynamischer Typisierung schreiben aber genügend Unit-Tests und so findet man die Fehler sowieso. Und die Unit-Tests muss man auch bei statisch typisierten Sprachen schreiben. Also gewinnt man etwas durch die statische Typisierung? Ein bisschen schon, aber es relativiert sich, wenn man seriös Unit-Tests schreibt.

Nun kann eine statische Typisierung den Vorteil haben, dass sie Variablen, Parameter und Rückgabewerte dokumentiert und zwar auf eine Art, die immer aktualisiert werden muss und nicht den Stand von vor ein paar Monaten widerspiegelt, der nie aktualisiert worden ist, weil das vergessen wurde oder die Zeit gefehlt hat.

Dynamische Typisierung kann vorteilhaft sein, wenn man Schnittstellen entwickelt, die mit einem Spektrum von Versionen von Software zusammenarbeiten können, indem man einfach die Typen zur Laufzeit erkennt und richtig behandelt. So kann man ein großes System robuster bauen, weil es zunehmend schwierig ist, ein größeres System komplett auf einmal zu aktualisieren, je größer dieses ist.

Share Button

Nokia schließt Verkauf der Mobiltelefonsparte an Microsoft ab

Mit Wirkung zum 25. April 2014 wird die Übernahme der (ehemaligen) Nokia-Mobiltelefonsparte durch Microsoft abgeschlossen.

Damit kommt eine merkwürdige Entwicklung zum Abschluss, die unabhängig von der juristischen Fragestellung sehr nach Korruption oder zumindest dubiosen Geschäften aussieht, weil der ehemalige Nokia-CEO recht offensichtlich mehr die Interessen seines ehemaligen Arbeitgebers Microsoft als seines dann aktuellen Arbeitgebers Nokia vertreten hat. Unter Elop (und nicht davor) ist Nokia vom Marktführer bei Smartphones zu einem Nischenanbieter geworden. Das mag sich mit den Symbian-Geräten abgezeichnet haben, aber es hätte plausible Ansätze gegeben, zumindest in der Spitzengruppe zu bleiben, wenn Meego oder Android als System eingesetzt worden wäre. Nun hat Samsung die Spitzenposition übernommen, die Nokia einst hatte. Apple war weltweit nie die Nummer eins und wird es wohl auch mit an Sicherheit grenzender Wahrscheinlichkeit nie werden. In einzelnen Märkten, z.B. in der Schweiz, haben sie aber immer noch große Marktanteile.

Und die innovativsten ehemaligen Nokia-Mitarbeiter haben eine neue Firma Jolla gegründet, die an Nokias innovativere Zeiten anzuknüpfen versucht, und vielversprechende Produkte anbietet. Wenn Jolla sich langfristig als Nischenanbieter etablieren kann, ist das sicher ein Erfolg und eine Bereicherung für den Markt.

Microsoft will die neue Abteilung unter dem Namen „Microsoft Mobile Oy“ betreiben. Sie haben die Rechte erworben, den Namen „Nokia“ und die Social-Media-Auftritte von Nokia für eine gewisse Zeit zu verwenden. Das ist nicht ungewöhnlich bei Übernahmen. Z.B. stellt die Firma Volvo seit 1999 keine Autos mehr her, aber sie haben die entsprechenden Aktivitäten verkauft und der aktuelle Besitzer kann auch nach 15 Jahren noch die Marke verwenden. Bei Nokia/Microsoft wird das aber nur für wenige Jahre möglich sein, außer die Verträge werden für entsprechende Zahlungen verlängert.

Share Button

Elixir II

Da ich das Glück hatte, an einer halbtägigen Elixir-Schulung teilzunehmen, möchte ich das Thema wieder aufgreifen.

Elixir ist eine relativ neue Programmiersprache, die sich von der Syntax an Ruby anlehnt. Die Programme laufen auf der Erlang-VM (BEAM). Diese VM ist anscheinend restriktiver als die Java-VM und so ist Elixir von den Konzepten her auch sehr nahe an Erlang.

Grundsätzlich ist Elixir wie Erlang eine funktionale Sprache, nicht wie Ruby objektorientiert. Es gibt aber die Möglichkeit, Strukturen aus Listen und Tupeln aufzubauen und weiterzugeben. Alle diese Strukturen sind immutable. Das heißt, dass Änderungen daran stets das Original unverändert lassen und eine Kopie mit den Änderungen generieren. Im Falle von verketteten Listen funktioniert das gut, wenn man nur am Anfang ein Element hinzufügt, weil dann kein eigentliches Kopieren erforderlich ist. Fügt man am Ende etwas hinzu, muss die Liste kopiert werden, was eine aufwändigere Operation ist. Es gibt keine Schleifen, wie in den meisten herkömmlichen Programmiersprachen, sondern man verwendet stattdessen Rekursion. Endrekursion sollte bevorzugt werden, weil das intern in Schleifen umgewandelt wird. Über die Parameterliste dieser Funktion kann man einen „Zustand“ weiterreichen.

Es ist sehr einfach und auch üblich, „Prozesse“ zu generieren. Dies sind nicht OS-Prozesse, sondern Konstrukte innerhalb von Erlang, man kann etwa an Aktoren denken, wie es bei Scala und Akka genannt wird. Wenn genug Hardware-Ressourcen vorhanden sind, kann man ohne weiteres mehrere Millionen solcher „Prozesse“ gleichzeitig haben. Jeder Prozess hat seinen eigenen Speicher und kann jedem anderen Prozess mit

send(...)

Nachrichten (messages) senden. Diese werden in eine sogenannte Mailbox (eine Warteschlange / engl. Queue) des Prozesses eingefügt und von diesem mit

receive(...)

empfangen. Das typische Muster ist, dass ein Prozess mittels Rekursion in einer Endlosschleife ist und jeweils mit receive Messages empfängt und verarbeitet.

Über diese Prozesse kann man doch so etwas wie einen Zustand modellieren. Man hat einfach einen Prozess mit so einer Endlosschleife und schickt ihm „set“- und „get“-Nachrichten. Die „set“-Nachrichten ändern den Zustand, der in der Endlosschleife über die Parameter weitergereicht wird, die get-Nachrichten lösen aus, dass eine Nachricht an den Absender geschickt wird, die dieser wiederum mit receive abfangen muss.

Funktionen mit gleichem Namen werden nach der Anzahl der Parameter, nicht aber nach dem Typ der Parameter, unterschieden. Die Sprache ist dynamisch typisiert. Man kann statt ein großes „if“ innerhalb einer Funktion zu haben, diese auch für verschiedene Fälle definieren und es wird für den tatsächlichen Aufrufparameter die richtige Teildefinition gefunden.

Share Button