8. Elm - Eine funktionale Programmiersprache zur Entwicklung von Web-Apps
8.4 HTTP-Requests
Bis jetzt hat unser Code passiv auf Nachrichten der Laufzeitumgebung gewartet.
Manchmal wollen wir aber von selbst tätig werden und Nachrichten an die
Umgebung schicken (z.B. Http-Requests). Diese "hinausgehenden" Nachrichten heißen
im Elm-Kontext commands
. Der entsprechende Datentyp
ist Cmd msg
, wobei msg
eine Typenvariable ist: bei
manchen Commands erwarten wir ja eine Antwort, die dann in Form einer Message hereinkommen
wird.
Zufallszahlen
Einfachstes Beispiel, Commands zu illustrieren, ist eine Webseite, die auf Knopfdruck
Zufallszahlen generiert. Zufallszahlen in die Hand zu bekommen ist in Elm erstaunlich
schwierig. Es gibt keine Funktion Math.random()
und kann sie auch nicht geben:
Math.random()
würde ja jedes Mal einen neuen Wert zurückliefern und wäre somit
gar keine Funktion! Sie sehen, Elm nimmt es sehr sehr ernst mit dem funktionalen
Paradigma.
Wie kriegen wir also eine Zufallszahl?
Starten Sie Website05Random.elm und schauen
Sie
sich den
Code an. Auf Knopfdruck schickt die Laufzeitumgebung unserem Code die Message
GenerateRandomNumber
; soweit konzeptuell alles beim Alten. Sehen
Sie sich aber Zeilen 45-47 an in der Funktion update
an:
case msg of
GenerateRandomNumber ->
( model, Random.generate NewRandomNumber (Random.float 0 1) )
Die Funktion update
gibt also nicht nur das neue Modell zurück,
sondern auch eine Anweisung an die Laufzeitumgebung. Versuchen wir
den Ausdruck Random.generate NewRandomNumber (Random.float 0 1)
zu entziffern:
Random.float 0 1
stellt einenRandom.Generator Float
her. Im Prinzip ist das eine Beschreibung der Art von Zufall, die wir wollen. Hier: einFloat
gleichverteilt im Intervall[0,1]
.NewRandomNumber
. Dies ist eineMessage
in unserem Code. Besser gesagt ein Datenkonstruktor. Die Message selbst istNewRandomNumber Float
, sie trägt also ein Float als Payload.- Die Funktion
Random.generate
baut dann daraus einenCmd
.
Die Semantik ist, dass Zeile 47 einen Cmd
erstellt und an die Laufzeit schickt;
diese berechnet dann eine neue Zufallszahl im Intervall [0,1]
und schickt
sie als Payload der Message NewRandomNumber
an unseren Code. Der kann dann
innerhalb der Funktion update
diese hereinkommende Nachricht weiterverarbeiten.
Http-Requests
An der Erzeugung von Zufallszahlen war überraschend, dass dies nicht innerhalb dies Elm-Codes geschehen konnte, sondern nur mithilfe der Umgebung zu bewerkstelligen war. Dies war natürlich dem strengen funktionalen Charakter von Elm geschuldet. Anders sieht es zum Beispiel bei Http-Requests aus: da liegt es in der Natur der Sache, dass sie nicht lokal im Code zu beantworten sind sondern, eventuell auch mit Zeitverzögerung, durch Kommunikation mit der Außenwelt. Das Comman`Message-Prinzip, das bei Zufallszahlen doch eher etwas gezwungen wirkte, kommt bei Http-Requests ganz natürlich ins Spiel.
In meiner "Minimalanwendung"
kann der Nutzer einen Suchbegriff eingeben und der Server antwortet dann etwa mit "Sie haben nach ... gesucht". Den Code finden Sie hier:- Client-Code in Elm: Website06Http.elm
- Server-Code in Node.js: Website06Server.js
SubmitButtonClicked ->
( { model | response = "" }
, Http.get
{ url = "http://localhost:8001/search?" ++ "key=" ++ model.input
, expect = Http.expectString ResponseReceived
}
)
Wir erstellen den Command mithilfe der Funktion Http.get
.
Diese erwartet zwei Argumente (technisch gesehen eins, das als Record wiederum zwei Werte
enthält).
Erstens den URL mitsamt Port; zweitens Instruktionen, was mit
einer Antwort zu tun wäre. Die Idee ist, dass, wenn die Laufzeitumgebung einen
Http-Response erhält, dieser an die Funktion ResponseReceived
übergeben wird
(typischerweise ist ResponseReceived
ein Datenkonstruktur und keine echte
Funktion),
der daraus eine Message baut und diese dann in der Funktion update
verarbeitet
wird.
Da bei Http-Requests aber Fehler auftreten können, ist der Datentyp des Responses
aber kein String
sondern ein
Result Http.Error String
. Dies ist ein Union-Datentyp ähnlich wie
Result
, und zwar
type Result err value
= Error err
| Ok value
Übungsaufgabe
Ich habe meinem Login-Server 13-login-server.zip
einen weiteren Endpunkt hinzugefügt: /fib-text
; ein Request auf
/fib-text?n=10
liefert
das Ergebnis, hier also 55
, als Text zurück und nicht als Json.
Schreiben Sie in Elm ein Fibonacci-Frontend. Der User eine Zahl eingeben. Klickt
er auf den Knopf compute
, dann schickt das Frontend einen
Http-Request zum Login-Server. Wenn das Ergebnis (als String) empfangen wird,
wird als Ergebnis auf der Seite ausgegeben.
Post-Requests
POST-Requests werden in Elm fast genau so behandelt wie GET-Requests. Die Funktion
Http.post
erwartet allerdings in ihrem Record-Argument noch einen
weiteren Wert: body: Http.Body
.
Das gesamte Code-Beispiel finden Sie hier:Http.post
{ url = "http://localhost:8001/add"
, body = Http.stringBody "application/json" ("{"key": "" ++ model.input ++ ""}")
, expect = Http.expectString ResponseReceived
}
- Client-Code in Elm: Website06HttpPost.elm
- Server-Code in Node.js: Website06Server.js
Übungsaufgabe Schreiben Sie eine App (Client in Elm, Server in Node.js) aufbauend auf den vorherigen Code-Beispielen, wo der Server ein einfaches Dictionary verwaltet (zum Beispiel tatsächlich ein Wörterbuch Deutsch-Englisch). Der Client besteht aus zwei Seiten: auf der einen können Sie nach Wörtern suchen; auf der anderen können Sie ein Wortpaar wie Katze, cat hinzufügen.
Der Server sollte mindestens die zwei Endpunkte GET /find?word=...
und POST /find?word=...&translation=...
zur Verfügung stellen.
Wenn Sie wollen, dürfen Sie auch eine delete
-Funktion implementieren.