Elm - finde ich super!

Coole Typen - Teil 4

von Martin Grotz, 2018-07-19

Im letzten Teil der Typen-Serie zu Elm geht es um zwei wichtige Konzepte, die einen großen Anteil daran haben, dass Elm seine "keine Laufzeitfehler"-Garantie wirklich leben kann: Maybe und Result.

type Maybe a
    = Just a
    | Nothing
Mit Maybe lässt sich der Fall ausdrücken, dass es einen Wert gibt - oder eben nicht. Mein bisher häufigster Anwendungsfall dafür sind optionale Eingabefelder. Ist kein Wert drin, haben wir eben Nothing. Und sobald was eingegeben wurde, ist Just a drin. Sonst benutzt man es auch für Funktionsparameter, die nicht unbedingt gesetzt sein müssen.
Elm kommt also zur Darstellung von "hier könnte was sein oder auch nicht" ohne Magic Values und auch ohne das gemeine NULL aus, das schon für so viel Kummer gesorgt hat.
Hat man nun aber einen Vorgang, der auch fehlschlagen kann, und man möchte auch die Information behalten, was genau schief gegangen ist, so gibt es dafür den Result-Datentyp:
type Result error value
    = Ok value
    | Err error
Hat alles geklappt, so steckt das Ergebnis im Ok. Gab es aber einen Fehler, so steckt dieser im Err-Fall.
Sowohl Maybe als auch Result sind im Endeffekt nur clever definierte Union Types. Und weil jeder Fehlerfall entweder über ein Maybe oder Result ausgedrückt wird, und wir bei Union Types verpflichtend alle definierten Fälle behandeln müssen, müssen wir uns für jede Stelle in unserem Programm überlegen, wie wir mit dem jeweiligen Fehlerfall genau umgehen. Dadurch können keine Fälle vergessen werden und es gibt keine unerwarteten Überraschungen zur Laufzeit.
Bei unserem ersten Projekt, einem kleinen Spiel, werden uns Maybe und Result allerdings eher selten begegnen, da dieses ohne Server oder komplizierte Daten-Konvertierungen auskommt. Hier haben wir fast alles selbst in der Hand und bewegen uns nur in der geschützten Elm-Welt.

Sicherheits-Upgrade im Hintergrund

von Martin Grotz, 2017-07-03

Heute gibt es nur ein kleines Sicherheits-Upgrade der Seite gegen die unerwünschte Weitergabe des Referers und außerdem habe ich die strikte Transportsicherheit angemacht. Die Seite https://webbkoll.dataskydd.net hat mir dabei sehr geholfen.

Coole Typen - Teil 3

von Martin Grotz, 2018-05-23

Jetzt geht es zu den Union Types. Diese haben sehr viele verschiedene Namen, je nach Programmiersprache oder Community in der man sich bewegt: Union Types, Discriminated Unions, Tagged Unions, Algebraic Datatypes oder auch Choice Types. Choice Types drückt für mich auch am ehesten aus, was man damit machen kann, ohne, dass man das ganze Hintergrundwissen dazu braucht:
Man kann damit eine feststehende, zur Kompilierzeit bereits bekannte Menge an verschiedenen Auswahlmöglichkeiten beschreiben. Union Types sind das wohl wichtigste Werkzeug, um eine Fachdomäne sauber und inklusive der Vermeidung von ungültigen Zuständen auszudrücken.

Zuerst einmal schauen wir uns Union Types ohne zusätzliche Daten pro Auswahl an:

type Hintergrundfarbe = Rot | Gelb | Blau

Wenn wir nun in unserer Webseite je nach Hintergrundfarbe einen passenden CSS-Hexcode ausspucken wollen, können wir eine Funktion schreiben, die mit einem case ... of die verschiedenen Fälle auswertet. Das nennt man dann Pattern Matching:

farbeZuHex : Hintergrundfarbe -> String
farbeZuHex hintergrundfarbe =
    case hintergrundfarbe of
        Rot ->
            "#ff0000"
        Gelb ->
            "#ffff00"              
        Blau ->
            "#0000ff"

Wichtig ist dabei auch: Elm erzwingt immer die Behandlung aller Pfade. Man kann zwar mit _ -> ... einen Standardfall festlegen, der immer dann ausgeführt wird, wenn kein anderer Zweig passt, aber dann hebelt man einen wichtigen Schutzmechanismus gegen Programmierfehler aus, daher ist nicht unbedingt empfehlenswert.

Eine weitere wichtige Einsatzmöglichkeit von Union Types sind die sogenannten Single Case Union Types. Diese dienen vor allem dazu, Funktionsparameter eindeutig festzulegen, so dass man nicht aus Versehen zum Beispiel zwei Floats beim Aufruf verdreht. Dies zu nutzen erhöht die Lesbarkeit und Sicherheit des eigenen Codes deutlich!

type Gewicht = Gewicht Float
type Groesse = Groesse Float
type alias Bmi = Float

errechneBmi : Gewicht -> Groesse -> Bmi
errechneBmi (Gewicht gewicht) (Groesse groesse) =
    gewicht / (groesse * groesse)

Im Gegensatz zu dem type alias Bmi, der letztendlich nur eine Umbenennung eines Floats darstellt, und damit auch überall dort als Argument erlaubt ist, wo ein Float erwartet wird, sorgen die beiden Single Case Unions dafür, dass die Reihenfolge wirklich eingehalten werden muss. Wäre die Funktionssignatur ein Float -> Float -> Float bestündie die sehr große Gefahr, dass man Gewicht und Größe beim Aufruf vertauscht und dabei sinnlose Ergebnisse erhält, obwohl der Compiler grünes Licht gibt.

In der berechneBmi-Funktion sieht man auch, wie man einen solchen Union Type auch gleich elegant "auspacken" kann, so dass man direkt mit den "enthaltenen" Floats arbeiten kann.

Auch viele interne Typen von Elm sind eigentlich Union Types, zum Beispiel Boolean oder das später noch genauer erläuterte Maybe bzw. Result.

Eine weitere wichtige Möglichkeit für den Einsatz von Union Types ist es, jedem einzelnen Fall unterschiedliche Daten mitgeben zu können. Das macht sie wesentlich mächtiger als die aus vielen anderen Sprachen bekannten switch-case-Statements:

type alias Radius = Float
type alias Laenge = Float
type alias Breite = Float

type Form
    = Kreis Radius
    | Rechteck Laenge Breite

flaeche : Form -> Float
flaeche form =
    case form of
        Kreis radius ->
            pi * (radius ^ 2)

        Rechteck laenge breite ->
            laenge * breite

Durch die geschickte Kombination von Union Types kann man innerhalb seiner Fachdomäne eine auch für Domänenexperten, die sich mit Programmierung nicht so gut auskennen, gut verständliche Beschreibung erstellen, die dann auch noch beim Programmieren wenig fehleranfällig ist. Deshalb bilden Union Types meist das Rückgrat jeder Elm-Typbeschreibung.

Die ausführlichere offizielle Doku zum Thema findet sich auf der Elm-Seite.

Coole Typen - Teil 2

von Martin Grotz, 2018-05-13

Wie angekündigt geht es in diesem Teil der Elm-Typen-Betrachung um Listen und Union Types. Beginnen wollen wir hierbei mit den Listen. Listen sind in Elm das Standardmittel um Mengen von Elementen zu verwalten. Listen sind, genau wie alle anderen Datentypen in Elm, unveränderlich ("immutable"), d.h. fügt man ein Element hinzu oder entfernt eines, so bekommt man immer eine Kopie der Liste zurück - das Original bleibt unverändert bestehen.

Listen werden einfach mit eckigen Klammern definiert. Mehrere Elemente trennt man mit einem Komma:

liste =
[ 1, 2, 3, 5, 7, 11 ]

Es gibt auch einige andere Möglichkeiten, Listen zu erzeugen. Hierzu verweise ich auf die Elm-Dokumentation zur Liste.

Es gibt auch noch eine Menge weiterer Funktionen, die auf Listen operieren. Besonders hervorheben möchte ich hier die Möglichkeit, via head auf das erste Element einer Liste zugreifen zu können - das machen wir uns in einem späteren Eintrag beim Einführen des Pattern Matching zu nutze, und map.

Mit map kann man über alle Elemente einer Liste iterieren und auf jedes Element eine Funktion anwenden. Das Ergebnis dieser Funktion wird dann an die gleiche Stelle einer neuen Liste geschrieben. Dies bildet eine mächtige Möglichkeit, alle Elemente einer Liste zu transformieren.

-- Listen
liste : List Int
liste =
    [ 1, 2, 3, 5, 7, 11 ]

quadriere : Int -> Int
quadriere a =
    a ^ a

quadrierteListe : List Int
quadrierteListe =
    List.map quadriere liste

Listen sind auch deshalb ein sehr wichtiger Bestandteil von Elm weil in allen view-Funktionen die HTML-Elemente jeweils Funktionen sind, die einerseits eine Liste von Attributen, andererseits eine Liste mit Kindelementen entgegennehmen. Daher kann man sich mit Hilfe der zahlreichen Listen-Funktionen sehr elegant seine HTML-Views bauen.

Für die Union Types gibt's dann doch noch einen eigenen Artikel, da diese vermutlich etwas mehr Raum und Zeit in Anspruch nehmen werden.

Coole Typen - Teil 1

von Martin Grotz, 2018-05-02

Bevor wir mit dem Umsetzen der Fachdomäne in Elm-Typen anfangen können müssen wir natürlich wissen, welche Typen es in Elm überhaupt gibt und was wir damit jeweils machen können. Im ersten Teil der dreiteiligen Artikel-Reihe geht es um die grundlegenden Typen:

Primitive Typen

Es gibt in Elm eine Reihe von grundlegenden Typen, die dann zu anderen Typen zusammengesetzt werden. Im Detail sind das:
  • Char: Einzelnes Zeichen
  • String: Zeichenketten
  • Bool: True oder False, um einen Wert darzustellen, der entweder Wahr oder Unwahr sein kann
  • Int: Ganze Zahl
  • Float: Kommazahl
  • number: Kann entweder Int oder Float sein - je nach tatsächlicher Verwendung
Als Elm-Code sieht das dann jeweils so aus:
c : Char
c =
    'a'

s : String
s =
    "Zeichenkette"

b : Bool
b =
    True

i : Int
i =
    42

f : Float
f =
    3.1415

n1 : number
n1 =
    42

n2 : number
n2 =
    3.1415

Records

Records sind unveränderliche Datenstrukturen, die aus mehreren Werten zusammengesetzt sind. Sie werden in Elm für gewöhnlich als Type-Alias definiert - theoretisch kann man aber auch direkt einen Record an Ort und Stelle definieren, ohne vorher einen passenden Type-Alias angelegt zu haben.

Wichtig: Records in Elm werden über ihren Inhalt verglichen - nicht über ihre Referenz. Außerdem: Records in Elm erfüllen dann eine Funktions-Signatur, wenn sie strukturell passen.

In Elm-Code sieht das dann so aus:

-- Record
somePoint =
    { x = 3, y = -1 }

-- Record mit dazugeschriebener Typdefinition
aPoint : { x : Int, y : Int }
aPoint =
    { x = 3, y = -1 }

-- Record Type für einfachere wiederholte Verwendung
type alias Point =
    { x : Int, y : Int }

anotherPoint : Point
anotherPoint =
    { x = 3, y = -1 }

-- beide Records erfüllen die Funktions-Signatur von someFunc, weil die Struktur jeweils passt
someFunc : Point -> Point -> Point
someFunc p1 p2 =
    { x = p1.x + p2.x, y = p1.y + p2.y }

x =
    someFunc somePoint anotherPoint

Noch mehr Infos zu Records gibt es in der offiziellen Elm-Doku.

Im nächsten Teil der Serie geht es dann um Union Types und Listen.

A Cure for Runtime Errors - Vortrag vom MATHEMA Campus 2018

von Martin Grotz, 2018-04-22

Ich hatte kürzlich die Gelegenheit, auf dem internen MATHEMA Campus vor einer Handvoll Zuhörer aus dem "Family&Friends"-Bereich meines Arbeitgebers einen kurzen Vortrag über Elm zu halten. Schwerpunkt war hierbei, wie Elm dabei hilft, Laufzeitfehler in Web-Frontends zu verhindern. Daher hieß der Vortrag dann auch "A Cure for Runtime Errors. Ich habe die Folien zum Vortrag als HTML-Seite hochgeladen.

Die Code-Beispiele finden sich auf meinem Github-Account.

Das erste Projekt: Ein Kartenspiel

von Martin Grotz, 2018-04-18

Am einfachsten lernt man mit einer neuen Programmiersprache meiner Meinung nach umzugehen, indem man ein konkretes kleines Projekt in Angriff nimmt.

Viele der Herausforderungen zeigen sich nämlich erst, wenn man zumindest ein klein wenig über Hello World hinausgeht.

Da es aber auch nicht zu kompliziert werden soll, habe ich mich für eine Einzelspieler-Browser-only-Variante eines Kartenspiels entschieden:
Die Regeln des Spiels "Schnarch Schnarch" sind überschaubar und auch im Internet verfügbar.

Im Laufe der Entwicklung werde ich auch immer wieder mal Artikel einschieben, die jeweils Funktionalität oder Syntax von Elm erklären, die ich danach dann gebrauchen werde. So wird es nicht zu viel trockene Theorie, aber sie fällt auch nicht ganz untern den Tisch!

Jetzt ist aber erstmal nur das Anlegen des Projektskeletts dran. Hierzu verwenden wir erneut die praktischen Befehle, die uns create-elm-app zur Verfügung stellt: elm-app create schnarchschnarch-sp-elm

Anschließend wird erstmal aufgeräumt: Im public-Ordner, der die statischen Ressourcen enthält, werfen wir alles außer index.html weg.

Nun folgt noch der source-Ordner, der den eigentlichen Elm-Quellcode enthält. Hier entfernen wir in der index.js-Datei erstmal die Verbindung zum ServiceWorker durch das Löschen der Zeilen import registerServiceWorker from './registerServiceWorker'; und registerServiceWorker(); Die zugehörige Datei registerServiceWorker.js wird gelöscht.

In Main.elm - dem Einsprungpunkt jedes Elm-Programms - vereinfachen wir die view-Funktion derart, dass sie folgendermaßen aussieht:

view : Model -> Html Msg
view model =
    div [] []
Dadurch wird nurmehr eine Seite mit einem leeren div erzeugt.

Damit ist die Leinwand vorbereitet, um jetzt mit dem nächsten Schritt weitermachen zu können: Die Fachdomäne analysieren und daraus möglichst gute Typdefinitionen zu erstellen.

Elm-Schnellstart

von Martin Grotz, 2018-04-17

Die Installation von Elm ist sehr einfach. Die einzige Voraussetzung ist ein aktuelles node.js.

Anschließend kann man alle wichtigen Tools und die Programmiersprache selbst mit einem einzigen Befehl herunterladen: npm install elm elm-format create-elm-app --global Danach können wir uns schon eine erste App erzeugen und in den neu erzeugten Ordner wechseln: elm-app create hello-world
cd hello-world
Dort starten wir den Entwicklungswebserver via elm-app start Damit sind wir auch schon fertig und können uns den Output unseres Elm-Programms im Browser anschauen. Dazu rufen wir die Standard-URL des Entwicklungswebservers auf: http://localhost:3000/