Records sind Datenstrukturen, die zusammengehörige Informationen unter Bezeichnungen bündeln. Sie ähneln Objekten in JavaScript oder Java oder Structs in C oder Rust, weisen aber einige wichtige Unterschiede auf.
Records werden mit geschweiften Klammern erstellt und ihre Elemente werden durch Kommas getrennt.
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
Der Typ eines Records wird ebenfalls mit einer ähnlichen Syntax definiert, nur dass Gleichheitszeichen = durch Doppelpunkte : ersetzt werden.
firefly : { name : String, creator : String, episodes : Int }
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
Diese Typdefinitionen immer zu wiederholen ist mühsam, deshalb ist es ein gängiges Vorgehen, ein Typ-Alias zu verwenden, um die Definition zu teilen.
type alias TvSeries =
{ name : String
, creator : String
, episodes : Int
}
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
Das Typ-Alias liefert außerdem automatisch eine Konstruktorfunktion für den Record. Das ist manchmal überraschend, denn Funktionen in Elm müssen mit einem Kleinbuchstaben beginnen, aber Typkonstruktoren sind eine Ausnahme.
firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14
Der wichtigste Weg, auf einen Feldwert einer Record-Instanz zuzugreifen, ist die Punktnotation wie firefly.creator.
Der Elm-Compiler unterstützt außerdem spezielle Zugriffsfunktionen, die mit einem Punkt . beginnen, wie .creator. Sie funktionieren mit jedem Record, der ein Feld mit diesem Namen hat.
Das ist die zweite Ausnahme von der Regel, dass Funktionen mit einem Kleinbuchstaben beginnen müssen.
Tatsächlich müssen selbst definierte Funktionen mit einem Kleinbuchstaben beginnen, aber die beiden Beispiele, die wir gerade entdeckt haben, erzeugt der Compiler, nicht der Programmierer.
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
firefly.name
--> "Firefly"
.creator firefly
--> "Joss Whedon"
Records können in Bindungen auch destrukturiert werden, und zwar mit dem Record-Musterabgleich. Das Destrukturieren funktioniert mit jedem Record, der Felder mit den passenden Namen hat.
episodesCount : TvSeries -> Int
episodesCount { episodes } =
episodes
complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
let
{ name, creator } = show
in
{ name = name
, creator = creator
, episodes = episodesCount show
}
Records sind unveränderlich, wie alles in Elm. Sobald ein Record definiert ist, können sich seine Feldwerte also nie mehr ändern.
Es gibt aber eine spezielle Syntax, um eine Kopie eines bestehenden Records zu erstellen und dabei ein oder mehrere Felder zu ändern.
Diese Syntax verwendet das Pipe-Symbol |, um den ursprünglichen Record von den Feldern zu unterscheiden, die sich ändern, zum Beispiel { oldRecord | field1 = newValue }.
firefly : TvSeries
firefly =
{ name = "Firefly"
, creator = "Joss Whedon"
, episodes = 14
}
updatedFirefly : TvSeries
updatedFirefly =
{ firefly | creator = "J Whedon", episodes = 15 }
Elm verwendet [strukturelle Gleichheit][equality]. Das bedeutet, dass zwei Instanzen desselben Records mit identischen Werten gleich sind.
firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14
firefly1 == firefly2
--> True
Elm unterstützt außerdem strukturelle Typisierung. Das bedeutet: Wenn eine Funktion einen Record mit einem x- und einem y-Feld benötigt, funktioniert sie mit jedem Record, der diese Felder hat, zum Beispiel mit 2D-Punkten, 3D-Punkten, Raumschiffen und so weiter.
Solche Record-Typen haben ein Pipe | in ihrer Definition, zum Beispiel { a | x : Float, y : Float }, und heißen in der Elm-Terminologie „erweiterbare Records“.
Achte auf den Unterschied zwischen einem Pipe | in einer Typdefinition, das heißt einer erweiterbaren Record-Definition ({ a | x : Int }), und einem Pipe | in einer tatsächlichen Record-Instanz, das bedeutet, dass wir einige Felder dieses Records aktualisieren.
point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }
.x point2d --> 1
.x point3d --> 3
length : { a | x : Float, y : Float } -> Float
length vector =
sqrt (vector.x * vector.x + vector.y * vector.y)
length point2d --> 2.236068
length point3d --> 5
translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
{ vector | x = vector.x + 1 }
translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }
In dieser Übung bist du ein großer Sportfan und hast gerade deine Leidenschaft für NBA-Basketball entdeckt. Da du neu im NBA-Basketball bist, tauchst du tief in die Geschichte der NBA ein, behältst Teams, Trainer, ihre Sieg- und Niederlagenstatistiken im Blick und vergleichst sie miteinander.
Da du noch kein Lieblingsteam hast, entwickelst du außerdem einen Algorithmus, der herausfindet, ob du für ein bestimmtes Team die Daumen drücken solltest. Sieben Aufgaben helfen dir dabei, deinen eigenen Daumen-drücken-für-ein-Team-Algorithmus zu entwickeln.
Definiere den Typalias Coach für einen Record mit den folgenden zwei Feldern, in dieser Reihenfolge:
name: der Name des Trainers, vom Typ String.formerPlayer: gibt an, ob der Trainer früher Spieler war, vom Typ Bool.Definiere den Typalias Stats für einen Record mit den folgenden zwei Feldern, in dieser Reihenfolge:
wins: die Anzahl der Siege, vom Typ Int.losses: die Anzahl der Niederlagen, vom Typ Int.Definiere den Typalias Team für einen Record mit den folgenden drei Feldern, in dieser Reihenfolge:
name: der Name des Teams, vom Typ String.coach: der Trainer des Teams, vom Typ Coach.stats: die Statistiken des Teams, vom Typ Stats.Du willst dich mit Code von Drittanbietern verbinden, der zwar ein Team-Konzept kennt, deinen Code aber mit den Parametern in einer anderen Reihenfolge aufruft. Füge eine Funktion createTeam mit der folgenden Signatur String -> Stats -> Coach -> Team hinzu.
Weil die Besitzer der NBA ungeduldig sind, hast du festgestellt, dass schlechte Teamergebnisse oft dazu führen, dass der Trainer ersetzt wird.
Implementiere die Funktion replaceCoach, die den neuen Trainer und das Team als Parameter nimmt und das Team mit dem neuen Trainer zurückgibt:
let
coach = { name = "Larry Bird", formerPlayer = True }
stats = { wins = 58, losses = 24 }
team = { name = "Indiana Pacers", coach = coach, stats = stats }
newCoach = { name = "Isiah Thomas", formerPlayer = True }
in
replaceCoach newCoach team
--> { name = "Indiana Pacers"
-- , coach = { name = "Isiah Thomas", formerPlayer = True }
-- , stats = { wins = 58, losses = 24 } }
In Zukunft willst du dir einen komplizierten Algorithmus ausdenken, der entscheidet, ob du für ein Team die Daumen drücken solltest oder nicht. Für den ersten Versuch hast du dich aber entschieden, dass du jedem Team die Daumen drückst, das mehr Siege als Niederlagen hat.
Du möchtest, dass diese Funktion auch in anderen Kontexten wiederverwendbar ist. Verwende deshalb einen erweiterbaren Record in der Typsignatur, der jeden Record annimmt, der die relevanten Felder hat.
Damit die Funktion so knapp wie möglich bleibt, willst du Pattern Matching verwenden, um die stats aus dem Funktionsparameter zu holen.
Implementiere die Funktion rootForTeam, die ein Team nimmt und True zurückgibt, wenn du für dieses Team die Daumen drücken solltest; andernfalls gib False zurück.
let
spursCoach = { name = "Gregg Popovich", formerPlayer = False }
spursStats = { wins = 56, losses = 26 }
spursTeam = { name = "San Antonio Spurs", coach = spursCoach, stats = spursStats }
in
rootForTeam spursTeam
--> True
Melde dich bei Exercism an, um Elm mit 28 Konzepte110 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.