Browse Month: August 2021

Pure Funktionen oder Lambda-Ausdrücke

Reine oder Pure Funktionen sind auch bekannt als anonyme Funktionen oder auch Lambda-Ausdrücke. Reine Funktionen zeichnen sich insbesondere dadurch aus, dass sie immer das gleiche Ergebnis zurückgeben, wenn sie mit den gleichen Argumenten aufgerufen werden. Dank dieser Eigenschaft, referenzielle Transparenz genannt, ist es für derartige Funktionen nicht möglich, Seiteneffekte zu besitzen. Eine reine Funktion ist also ein rechnerisches Analogon einer mathematischen Funktion.

Eine Pure Function ist demnach eine ganz normale Programmfunktion, die lediglich zwei besondere Eigenschaft hat:

  1. Für die gleichen Eingabeparameter wird stets der gleiche Rückgabewert geliefert
  2. Es existieren keine Side-Effects (Nebenwirkungen)

Durch diese beiden Merkmale ist eine Pure Function vergleichbar mit einer schlichten mathematischen Funktion y = f(x), die für denselben x-Wert immer denselben y-Wert ermittelt.

Das bedeutet in der Praxis, dass die gesamte Programmlogik einer Funktion allein mit den Daten aus den Eingabeparametern arbeitet und als Ergebnis genau ein Rückgabewert entsteht. Während des Funktionsaufrufes entstehen keinerlei Side-Effects, wie beispielsweise das Verändern des Zustandes von Variablen außerhalb der Funktion oder der Eingabeparameter, das Schreiben in die Datenbank bzw. einer Datei oder der Aufruf anderer Funktionen, die selbst Side-Effects besitzen.

Das Konzept der Pure Function lässt sich nicht nur relativ leicht erklären, sondern ermöglicht in der Praxis Software zu schreiben, die vom Entwickler einfach zu verstehen ist, da die einzelnen Funktionen untereinander keine Abhängigkeiten besitzen und für sich allein geschrieben, verstanden und getestet werden können. Dies ist ein riesiger Vorteil. 

Ein Großteil der heute geschriebenen Software folgt jedoch einem anderen Paradigma, dem objektorientierten Modell (OO-Modell), welches genau das Gegenteil propagiert. In der objektorientierten Welt werden Funktionen als Methoden bezeichnet und zusammen mit den dazugehörigen Daten von Objekten (Klassen) gekapselt. Beim Aufruf einer Methode kann diese sowohl auf die übergebenen Eingabeparameter als auch auf die Daten des Objektes selbst zugreifen. Dabei wird bewusst der Zustand eines Objektes über die entsprechenden Methoden manipuliert, es werden also die Daten im Objekt selbst verändert. Zusätzlich werden die Daten eines Objektes als Rückgabewert eines Methodenaufrufes an andere Objekte weitergereicht und unterliegen dort der weiteren Manipulation.

Beispiel von Pure Funktion in der Wolfram Language

Es gibt mehrere gleichwertige Möglichkeiten, reine Funktionen in der Wolfram Language zu schreiben. Die Idee besteht in allen Fällen darin, ein Objekt zu konstruieren, das mit entsprechenden Argumenten eine bestimmte Funktion berechnet. Wenn also beispielsweise fun eine reine Funktion ist, dann wertet fun[a] die Funktion mit dem Argument a aus.

Die Wolfram Language erlaubt die Verwendung sogenannter reiner Funktionen (pure functions). Ihr erstes Argument wird gekennzeichnet durch das “#” Symbol und deren Ende ist mit dem “&”-Symbol gekennzeichnet.

So wie der Name einer Funktion irrelevant ist, wenn Sie nicht erneut auf die Funktion verweisen möchten, sind auch die Namen von Argumenten in einer reinen Funktion irrelevant. Die Wolfram Language ermöglicht es Ihnen, explizite Namen für die Argumente reiner Funktionen zu vermeiden und stattdessen die Argumente durch die Angabe von “Slot-Nummern” #n zu spezifizieren. In einer reinen Funktion von Wolfram Language steht #n für das n-te Argument, das man angeben kann. # steht für das erste Argument. . #2 steht für das zweite Argument usw.

Das nachfolgende Beispiel soll dieses Vorgehen veranschaulichen. Es soll eine Funktion definiert werden, die immer 1 addiert:

(#+1)& 

Wenn diese Funktion nun als Kopf eines Ausdrucks angegeben wird, so wird die Funktion auf alle Argumente angewendet:

(#+1)&[50]  --->  51
(#+1)&[{50,60}] ---> 51,61

Das geht natürlich auch mit mehreren Argumenten, wie das nachfolgende Beispiel zeigt:

{#2, 1 + #1, #1 + #2} &[a, b]  ---> {b, 1+a, a+b}

Grundsätzlich lassen sich beliebig viele Argumente definieren. Ab einer bestimmten Anzahl wird es aber doch sehr herausfordernd, noch die Übersicht zu behalten. Reine Funktionen in der Wolfram Language können eine beliebige Anzahl von Argumenten annehmen. Sie können ## verwenden, um alle angegebenen Argumente anzusprechen, und ##n für das n-te und nachfolgende Argumente.

f[##, ##] &[x, y]  ---> f[x,y,x,y]

Reine Funktionen ermöglichen es, Funktionen anzugeben, die auf Argumente angewendet werden können, ohne explizite Namen für die Funktionen definieren zu müssen.

Weitere Beispiele…

#^2& is a short form for a pure function that squares its argument:

Map[#^2 &, a + b + c]


This applies a function that takes the first two elements from each list. By using a pure function, you avoid having to define the function separately:

Map[Take[#, 2] &, {{2, 1, 7}, {4, 1, 5}, {3, 1, 2}}]

Berechnung des Oster-Datums

Methode nach C.F. Gauß

Die Berechnung des Oster-Datums erfolgt nach Regeln, die im Jahre 325 auf dem Konzil von Nicäa beschlossen wurden. Dem zu Folge das Osterfest am ersten Sonntag nach dem Frühlingsvollmond gefeiert werden soll. Fällt aber der erste Frühlingsvollmond auf einen Sonntag, so solle das Osterfest eine Woche später erfolgen. Da der Frühlingsvollmond im gregorianischen Kalender frühestens am 21. März und spätestens am 18. April eines Jahres liegen kann, schwankt der Ostertermin demnach immer zwischen dem 22. März und dem 25. April.

Der Algorithmus von C.F. Gauß besteht aus einer Abfolge von Rechenschritten die im folgenden dargestellt sind:

Ausgangspunkt ist immer eine Jahreszahl “Jahr” für die das Oster-Datum berechnet werden soll.

Erster Schritt: Bestimmung der erforderlichen Hilfszahlen

M und N seien zwei Hilfszahlen, die sich aus folgendem Algorithmus berechnen:

M = Rest der Division von (15 - p + k - q) : 30 
N = Rest der Divison von (4 + k - q) : 7 
k = Jahreszahl ohne die letzten beiden Ziffern  
p = Ganzzahliger Anteil der Division (13 + 8k) : 25
q = Ganzzahliger Anteil der Division k : 4


Im Gregorianischen Kalender liegen die Werte für die Jahre 1900 bis 2099 immer bei M= 24 und N = 5. Über die obige Rechenformel lassen sich aber auch Werte die vor 19oo liegen bzw. solche die ab 2100 dann Gültigkeit besitzen.

Zweiter Schritt: Berechnung der weiteren Werte

a = Rest der Division Jahr : 19
b = Rest der Division Jahr : 4
c = Rest der Division Jahr : 7
d = Rest der Division (19a + M) : 30
e = Rest der Division (2b + 4c + 6d + N) : 7

Berechnung des Oster-Datums

Ostern ist dann der

Ostern = (22 + d + e)te März, sofern der Wert zwischen 1 und 31 liegt, 
sonst ist 
Ostern = (d + e -9)te April

Es gibt aber wie oben beschrieben einige Randbedingungen zusätzlich zu berücksichtigen:

Ist d = 29 und e = 6,            dann ist Ostern nicht am 26. April sondern am 19. April
Ist d = 28 und e = 6 und a > 10, dann ist Ostern nicht am 25. April sondern am 18. April

Programmtechnische Umsetzung

Damit haben wir alle Schritte zusammen, die sich C.F. Gauß überlegt hat und können nun mit Hilfe beliebiger Programmiersprachen ein Programm erstellen, mit dessen Hilfe das Oster-Datem bestimmt werden kann. In Mathematica (Wolfram Language) könnte das wie folgt berechnet werden:

M=24; Nu=5;

For[Jahr = 1950, Jahr < 1980, Jahr++, a = Mod[Jahr, 19];
 b = Mod[Jahr, 4];
 c = Mod[Jahr, 7];
 d = Mod[(19*a + M), 30];
 e = Mod[(2 b + 4 c + 6 d + Nu), 7];
 
 Ostern1 = (22 + d + e);
 Ostern2 = (d + e - 9);
 
 If[(d == 29 && e == 6), Ostern2 = 19, Ostern2 = (d + e - 9)];
 If[(d == 28 && e == 6 && a > 10), Ostern2 = 18, 
  Ostern2 = (d + e - 9)];
 
 If[(Ostern1 < 1 || Ostern1 <= 31), {Print[Jahr, " Ostern ist am ", 
    Ostern1, ". März"]; Print[]}, Print["Ostern liegt im April"]];
 
 If[(Ostern2 >= 1 ), {Print[Jahr, " Ostern ist am ", Ostern2, 
    ". April"]; Print[]}, {Print["Ostern liegt im März s.o."], 
   Print[]}]]

Das könnte man natürlich noch viel eleganter ausführen, aber für einen ersten Test des Algorithmus müsste die Umsetzung genügen:

1950 Ostern ist am 9. April

1951 Ostern ist am 25. März

1952 Ostern ist am 13. April

1953 Ostern ist am 5. April
 
1954 Ostern ist am 18. April
 
:
:
:

1976 Ostern ist am 18. April

1977 Ostern ist am 10. April

1978 Ostern ist am 26. März
 
1979 Ostern ist am 15. April