2. Elm

2.1 Elm als Taschenrechner: das Repl-Fenster

Repl ist eine Abkürzung und steht für Read, Evaluate, Print, Loop. Genauer:
  • Read. Das Fenster wartet darauf, dass der Benutzer einen Ausdruck (zum Beispiel (3 + (4 * 5))^2) eingibt.
  • Evaluate. Elm evaluiert den Ausdruck, wertet ihn also aus.
  • Print. Es druckt das Ergebnis auf der Konsole aus.
  • Loop. Es läuft in einer Dauerschleife, springt also wieder zu Read zurück, bis wir es beenden.

Wir werden nun die Dinge aus dem vorherigen Kapitel im Repl-Fenster von Elm schreiben. In diesem Abschnitt machen einen kurzen Schnelldurchgang. Danach werden wir alles noch einmal genauer unter die Lupe nehmen.

Ausdrücke und Identifier

Öffnen Sie eine Konsole und geben Sie elm repl ein. Dann tippen Sie
(3 + (4 * 5))^2
und sehen, was geschieht. Sie können das Repl-Fenster also wie einen Taschenrechner verwenden. Sehen wir als nächstes, wie man Bezeichner (Identifier) einführt. Geben Sie ein:
stundenProTag = 24                        
minutenProStunde = 60
sekundenProMinute = 60 
stundenProTag * minutenProStunde * sekundenProMinute

Hier werden mehrere Bezeichner eingeführt. Der String stundenProTag hat ab sofort einen Wert, und zwar 24. Er kann von nun an wie eine Zahl in jedem Kontext verwendet werden.

Funktionen

Erinnern Sie sich, wie ich in Kapitel 1.2 informell Funktionen definiert habe, zum Beispiel f(n) = n * (n+1) / 2. In Elm geht das ähnlich, allerdings ohne die in der Mathematik üblichen Klammern:

f n = n * (n+1) / 2

Auch hier wird ein neuer Bezeichner eingeführt: f. Allerdings erkennt Elm, dass vor dem = noch etwas kommt, nämlich das n. Elm weiß nun also, dass es sich bei f um eine Funktion mit Parameter n und Funktionskörper n * (n+1) / 2 handelt. Wir können f jetzt aufrufen:

f 5                        
15

Beachten Sie, dass wir in Elm nicht f (n) sondern f n schreiben. Die Argumente werden einfach rechts vom Funktionsnamen geschrieben, mit Leerzeichen separiert, ohne Komma. Natürlich können wir so einen Funktionsaufruf wiederum in komplexeren Ausdrücken verwenden:

f 5 * f 6
Übungsaufgabe Wie interpretiert Elm den Ausdruck f 5 * 3? Da gibt es zwei Möglichkeiten:
  1. Es wird erst 5 * 3 ausgewertet und das Ergebnis dann der Funktion f als Eingabe-Argument übergeben.
  2. Es wird erst f 5 ausgewertet und dann das Ergebnis mit 3 multipliziert.
Was ist denn nun der Fall? Experimentieren Sie und stellen Sie es fest!

Operatorenpräzedenz

In der Schule haben Sie wahrscheinlich die Regel Punkt vor Strich kennengelernt. Diese besagt, dass \begin{align*} 3 + 4 \cdot 5 \end{align*} eben als \(3 + (4 \cdot 5)\) und nicht als \((3 + 4) \cdot 5\) zu interpretieren ist. Das \(\cdot\) klebt stärker als das \(+\). Hierbei handelt es sich nicht um ein Naturgesetz, sondern einfach um eine Konvention, um Klammern einzusparen. Funktionsaufrufe in Elm "kleben" nun noch stärker als \(\cdot\), so dass eben f 5 * 3 als (f 5) * 3 interpretiert wird. Falls Sie aber f (5 * 3) meinen, also wollen, dass zuerst 5 * 3 ausgewertet wird, dann müssen Sie diese Klammern auch setzen, also eben auch f (5 * 3) schreiben.

Funktionen können natürlich auch mehrere Parameter haben. Betrachten Sie \begin{align*} g(x,y) = x^2 + y^2 \ . \end{align*} In Elm können wir \(g\) wie folgt definieren:

g x y = x^2 + y^2

und aufrufen:

g 3 4    
25 : number

Wir haben nun also zwei Funktionen definiert: die einstellige Funktion \(f\) und die zweistellige Funktion \(g\). Wie können wir nun Dinge wie \(g(2, f(5))\) in Elm schreiben? Probieren Sie es aus:

g 2 f 5

Sie sehen nun eine Fehlermeldung. Der entscheidende Satz ist: The `g` function expects 2 arguments, but it got 3 instead. Sehen Sie, Elm interpretiert den Ausdruck g 2 f 5 als Aufruf der Funktion g mit drei Argumenten, nämlich 2, f und 5. Die Funktion g nimmt aber nur zwei Argumente. Daher Fehlermeldung. Wenn Sie also \(g(2, f(5))\) meinen, dann müssen Sie das entsprechend klamern:

g 2 (f 5)

Nun sieht Elm den Funktionsnamen g mit zwei Argumenten: 2 und f 5, und alles ist gut. Intern geschieht mit diesem Ausdruck also folgendes:

g 2 (f 5)
g 2 (5 * (5 + 1) / 2)
g 2 (5 * 6 / 2)
g 2 (30 / 2)
g 2 15 
2^2 + 15^2 
4 + 225 
229

Fallunterscheidungen

In Kapitel 1.3 habe ich über Fallunterscheidungen gesprochen. Einführendes Beispiel war die Absolutwertfunktion, die ich als abs(x) = if x >= 0 then x else -x geschrieben habe. In Elm geht das ganz genau so:
absVal x = if x >= 0 then x else -x

Ich habe hier absVal statt abs geschrieben, weil Elm die Funktion abs bereits "von Haus aus" kennt. Damit also keine Verwechslungen auftreten. Die if-then-else-Konstruktion kann natürlich auch verschachtelt werden; das heißt: hinter dem else darf wiederum eine Fallunterscheidung stehen:

h x = if x <= -1 then -1 else (if x <= 1 then x else 1)
In diesem Falle dürfen wir die Klammern auch weglassen, weil es keine Verwechslungsgefahr gibt.

Strings

Als wichtigen Datentyp neben Zahlen haben wir im Kapitel 1.4 die Strings kennengelernt. In Elm gibt es für Strings bereits viele vorgefertigte Funktionen. Tippen Sie mir nach:
wort = "Bekanntmachung"                          
String.right 3 wort
"ung" : String
String.left 4 wort
"Beka" : String
wort ++ "en"
"Bekanntmachungen" : String
String.length wort
14 : Int

Versuchen wir jetzt, unsere einfache "Präteritum"-Funktion zu schreiben, die die letzten beiden Buchstaben entfernt und durch "te" ersetzt:

removeRight word k = String.left (String.length word - k) word
removeRight "kochen" 2
"koch"

Diese Funktion berechnet die Wortlänge, hier \(n\) genannt, zieht davon \(k\) ab, und nimmt also die ersten \(n-k\) Zeichen des Wortes.

insPraeteritum word = removeRight word 2 ++ "te"                        
insPraeteritum "kochen"
"kochte" : String
Übungsaufgabe Ergänzen Sie die Funktion insPraeteritum mit Fallunterscheidungen, damit es für Verben wie schalten, walten, baden, angeln, krabbeln, knabbern korrekt funktioniert. Tip. Mit String.right 2 wort erhalten Sie die letzten zwei Buchstaben des Wortes. Sie können dann beispielsweise mit if String.right 2 wort == "ln" testen, ob das Wort auf "ln" endet.