8. Elm - Eine funktionale Programmiersprache zur Entwicklung von Web-Apps
8.1 Ein Crashkurs in Elm
Funktionen, Signaturen, Rekursion
Schreiben wir nun unsere erste Elm-Funktion sqr. Sie soll das
Quadrat einer Zahl berechnen. Ähnlich wie in Java oder C müssen wir die
Signatur der Funktion angeben. In Java wäre dies zum Beispiel
public float sqr (float x)
In Elm sieht das so aus:
sqr : Float -> Floatsqr x =x * x
Gehen Sie nun mit der Konsole in das Verzeichnis elm (nicht
elm/src)
und geben ein
elm replsqr 2.5
Das sollte eine Fehlermeldung geben.
Sie müssen das Modul Test erst noch importieren. Also, noch einmal:
import Test exposing (..)sqr 2.5
Als Ergebnis sollte nun 6.25 : Float erscheinen. Sie
sehen:
bei Ausgabewerten gibt Elm immer den Typ des Objekts mit an.
Dinge wie if,then,else und Rekursion funktionieren ohne größere Überraschungen:
fib : Int -> Intfib n =if n == 0 then0else if n == 1 then1elsefib (n - 1) + fib (n - 2)
Wenn Sie das Fehlen des Schlüsselwortes return verwirrt,
dann rufen Sie sich ins Gedächtnis, dass Elm eine funktionale Sprache ist.
Es gibt also gar keine Anweisungen / statements, daher ist jede Zeile in einer
Funktion,
in der nichts Anderes (wie z.B. if oder so) steht, eine "return"-Zeile, und das
Schlüsselwort return somit redundant.
Gehen Sie nun auf die Konsole, wo noch Ihr elm-repl-Fenster auf ist, und tippen ein
fib 5
Sie müssen nicht noch einmal import Test exposing (..) eingeben.
Das Repl-Fenster "überwacht" die Datei sozusagen und bekommt jede Änderung mit. Sie können
natürlich in Ihrer Datei auch Konstanten definieren, zum Beispiel
n_0 : Intn_0 =25
Primitive und fest eingebaute Datentypen
Es gibt die gleichen fundamentalen Datentypen wie in anderen Programmiersprachen auch:
s = "Hello""Hello" : String'H''H' : Chars == "Hello"True : Bools == "Hallo"False : Bools == 'H'-- komplexe Fehlermeldung, weil die Typen nicht übereinstimmen
Es gibt also String, Char, Bool wie in anderen Sprachen auch.
Records / Structs
Wir können Record-Typen anlegen, um Daten zu bündeln, ähnlich demstruct in C:
type alias Datum ={ jahr : Int, monat : Int, tag : Int}type alias Student ={ name : String, matrikelNummer : Int, geburtsDatum : Datum}s1 : Students1 ={ name = "Johannes Schneider", matrikelNummer = 6251294, geburtsDatum = { jahr = 2000, monat = 1, tag = 1 }}
und dann im Repl-Fenster
s1.name"Johannes Schneider" : Strings1.geburtsDatum{ jahr = 2000, monat = 1, tag = 1 } : Datums1.geburtsDatum.jahr2000 : Int
Übungsaufgabe
Schreiben Sie einen Record-Typ IntPaar, das
paar.erstes und paar.zweites hat.
Schreiben Sie nun eine "schnelle" rekursive funktion
fibFast : Int -> IntPaar,
die hohe Fibonacci-Zahlen wie zum Beispiel
\(F_n\) ohne Probleme berechnen kann.
Tip. Sie werden höchstwahrscheinlich eine
Helfer-Funktion nextPair
oder so brauchen, die aus \(a,b\) das Paar
\(b, a+b\) macht.
Zwischenergebnisse mit let / in
Sie können in Elm Zwischenergebnisse berechnen und ihnen eine Wert zuweisen.
In Racket hatten sie hierfür let; das Schlüsselwort in Elm heißt auch
let, allerdings ist die Syntax weniger exotisch.
Sie sehen jetzt die Lösung für die obige Übungsaufgabe, allerdings mit let,
sodass wir keine Helferfunktion nextPair mehr brauchen.
type alias IntPaar ={ erstes : Int, zweites : Int}fibFast : Int -> IntPaarfibFast n =if n == 0 then{ erstes = 0, zweites = 1 }elseletpaar : IntPaarpaar =fibFast (n - 1)in{ erstes = paar.zweites, zweites = paar.erstes + paar.zweites }
In Zeilen 60/61 deklariere/definiere ich eine Zwischenergebnis-Variable paar.
Nach dem Schlüsselwort in kommt der return-Wert der Funktion, und in dem
darf ich dann nach herzenslust die zuvor in let definierten Bezeichner
verwenden.
Datentypen unbürokratisch mit Tuple erschaffen
Wenn Sie nur schnell mal zwei Werte bündeln wollen, dann müssen Sie nicht extra
einen Datentyp dafür erschaffen; Sie können einfach
paar = (2,3) schreiben und dann mit
Tuple.first paar und
Tuple.second paar auf die beiden
Stellen zugreifen. Das macht den Code von fibFast einfacher.
ff : Int -> ( Int, Int )ff n =if n == 0 then( 0, 1 )elseletpaar =ff (n - 1)in( Tuple.second paar, Tuple.first paar + Tuple.second paar )
Listen, Pattern matching, map, filter
To be done.Currying
Elm verwendet für +, *, < etc. Infix-Notation (im Gegensatz zu Racket).
Wenn Sie aber Präfixnotation wollen, dann kriegen Sie die mit
(+), (*), (<). In der Tat:
(+) 3 58 : number(<) 3 5True : Bool(<)<function> : comparable -> comparable -> Bool
Die letzte Zeile sagt Ihnen, dass (<) eine Funktion ist,
die zwei Objekte vom Type comparable (also z.B. Int)
nimmt und Bool zurückliefert.
Was geschieht, wenn wir (<) mit "zu wenig" Argumenten aufrufen?
(<) 3<function> : number -> Boolf = (<) 3<function> : number -> Bool
Wenn Sie in Elm eine Funktion mit "zu wenig" Argumenten aufrufen, dann erzeugt dies keine
Fehlermeldung, sondern eine neue Funktion, die geduldig auf die restlichen Argumente
wartet. Die gerade definierte Funktion f wartet also auf ein
comparable und, wenn es dies erhält:
f 4True : Boolf 5True : Boolf 2False : Bool
Wir haben mit f = (<); 3 also eine Funktion greaterThan3
erschaffen.
Diese Prinzip, Funktionen nur mit einer Teilliste ihrer Argumente aufzurufen, nennt man
Currying.
Jetzt macht auch die Signatur von z.B. intRangemehr Sinn:
intRange : Int -> Int -> List Int
Expliziter könnten wir schreiben:
intRange : Int -> (Int -> List Int)
Das hieße dann, dass intRange, mit einem Int aufgerufen,
eine Funktion zurückgibt, die wiederum ein Int als Argument nimmt,
und daraus eine Liste bastelt. In der Tat
g = intRange 5<function> : Int -> List Intg 10[5,6,7,8,9,10] : List Intg 5[5] : List Int
Wenn Sie List.map, List.filter, List.foldl und
Currying verstehen, dann werden Sie kaum selbst rekursive Funktionen schreiben müssen.
Ein Beispiel, das Currying und List.filter verwendet, ist
der folgende kleine Code, der aus einer Liste alle Elemente herausfischt, die
größer sind als 3:
List.filter ( (<) 3 ) [6,1,4,2,3,5][6,4,5
Übungsaufgabe Implementieren Sie Quicksort in Elm. Zur Erinnerung: hier ist der Pseudocode:
quicksort(list):
if list == []: return []
else:
pivot = list[0]
smaller = those in list that are smaller than pivot
equal = those in list that are equal to pivot
greater = those in list that are greater than pivot
return quicksort(smaller) ++ equal ++ quicksort(greater) // append all three lists
Union-Datentypen
Hier ist ein Teilcode für eine Binary-Search-Tree-Typ.
module BST exposing (..)type BST= Empty| Node Int BST BST-- Sie können einen Baum nun zum Beispiel per-- Node 3 (Node 4 Empty Empty) Empty erschaffen.contained : Int -> BST -> Boolcontained key tree =case tree ofEmpty ->FalseNode keyHere leftTree rightTree ->if (key == keyHere) thenTrueelse if key < keyHere thencontained key leftTreeelsecontained key rightTreeinsert : Int -> BST -> BSTinsert key tree =
Übungsaufgabe
Vollenden Sie die Funktion insert.