Jetzt wo wir bereits die Datenstrukturen Listen, Arrays, Sets und Dictionaries als auch Records und Tupel kennen, gibt es nur noch eine weitere Basisstruktur, über die wir sprechen müssen: Den sogenannten Union Type.
Durch die Definition eines Union Type erstellt man immer einen neuen Typ. Im Gegensatz zu den anderen Datenstrukturen, die wir bisher kennengelernt haben, kann ein Union Type eine Vereinigung verschiedener Typen sein – jedoch muss er dies nicht.
In seiner einfachsten Form kann ein Union Type als eine Art Aufzählung oder Enumeration betrachtet werden:
type PullRequestState
= Proposed
| Rejected
| Merged
Dieser Ausdruck erstellt den Typ PullRequestState, welcher einen der drei definierten Werte annehmen kann. Diese Werte werden auch Tags genannt und dienen dazu, zwischen den einzelnen Möglichkeiten zu unterscheiden, wenn man den Union Type in einer Case-Anweisung verarbeitet. Abhängig vom Tag können dabei verschiedene Aktionen ausgeführt werden:
branchColor : PullRequestState -> String
branchColor state =
case state of
Proposed ->
"yellow"
Rejected ->
"red"
Merged ->
"green"
So weit, so unspannend – der Spaß beginnt, sobald wir anfangen Zusammenstellungen verschiedener Typen zu verwenden. Dadurch lassen sich Union Types als State Machines oder sogar als verschiedene “Klasses” von Daten betrachten.
Sehen wir uns diesen Fal in einem Beispiel an, in dem wir die Verzfügbarkeit eines Produkts modellieren:
module Main exposing (..)
type Availability
= SoldOut
| InStock Int
| Reordered ( Int, Int )
| Announced String
displayStatus : Availability -> String
displayStatus availability =
case availability of
SoldOut ->
"Sold out"
InStock amount ->
"In stock: " ++ (toString amount) ++ " left."
Reordered days ->
let
min =
toString (first days)
max =
toString (second days)
in
"Available again in " ++ min ++ " to " ++ max ++ " days."
Announced date ->
"Available on " ++ date ++ "."
Auf diese Art haben wir die Möglichkeit, zusätzliche Informationen an das Tag anzuhängen. Und diese weiteren Informationen können von verschiedensten Typen sein, wie in diesem Beispiel …
- ein Integer der die Anzahl des Bestands beim Tag
InStock
darstellt - ein Tupel von Zahlen, welche die Spanne der Tage bis zur Wiederverfügbarkeit des Produkts im Fall des
Reordered
Tags angeben - Ein String mit dem Veröffentlichungsdatum für das
Announced
Tag
displayStatus (InStock 42)
-- "In stock: 42 left." : String
displayStatus (Reordered (3,5))
-- "Available again in 3 to 5 days." : String
displayStatus (Announced "2016-12-24")
-- "Available on 2016-12-24." : String
Jeder dieser Einträge hat eine andere Form, doch alle Einträge sind vom gleichen Union Type, weshalb wir sie kombinieren können und einen einheitlichen Weg haben können, um sie verarbeiten zu können. Zum Beispiel können wir eine Liste von Availability
-Einträgen erstellen und die Stati anzeigen – unabhängig von ihrer jeweiligen Form:
availabilities : List Availability
availabilities =
[ SoldOut
, InStock 42
, Reordered ( 3, 5 )
]
List.map displayStatus availabilities
-- ["Sold out","In stock: 42 left.","Available again in 3 to 5 days."] : List String
Dadurch haben wir einen einfachen Weg, um Fälle zu behandeln, in denen wir beispielsweise eine Art Subklasse modellieren, wenn wir in der OOP-Terminologie denken.
Das wär’s: Nach dieser dreiteiligen Artikelserie kennst du die Basis-Datenstrukturen in Elm. In den nächsten Schritten sehen wir uns den Typ Maybe
an und betrachten Case-Anweisungen und Pattern Matching etwas näher. Danach haben wir dann die Grundlagen abgeschlossen und kümmern uns um fortgeschrittenere Themen :)