2. Elm
2.3 Funktionen definieren und aufrufen; einfache Datentypen
Alle Ausdrücke in einem Elm-Programm haben einen Typ:
wort = "angeln"
wort : String
String.length wort
6 : Int
radius = 2.3
radius : Float
Wir sehen hier bereits drei Datentypen: String
, also Zeichenketten;
Int
für ganze Zahlen; Float
für Gleitkommazahlen (Zahlen, die
eventuell Nachkommastellen
haben). Ach ja, Zahlen: die stellen in Elm einen Sonderfall dar, was den Typ anbelangt:
n = 13
n : number
Da Elm nicht weiß, ob wir 13 als ganze Zahl oder als Gleitkommazahl behandeln wollen,
führt es es unter dem Typ number
. Der Typ number
ist
eine Art "Obertyp", der eben Int
als auch Float
sein kann.
Das ist keine Prinzipienreiterei: wie die Zahl 13 drinnen im Rechner dargestellt ist, hängt
in der Tat davon ab, ob sie als Int
oder als Float
angesehen wird.
Sie können für jeden Bezeichner den Typ herausfinden, indem Sie ihn einfach im Repl-Fenster eintippen, so wie oben. Das geht auch mit Funktionen:
import Session1 exposing (..)
removeRight
<function> : String -> Int -> String
Was sagt Ihnen Elm hier? Dass removeRight
eine Funktion ist,
als Inputparameter einen String
und ein Int
nimmt
und einen String
zurückgibt. Der Typ von removeRight
ist
String -> Int -> String
! Wir nennen das auch die Signatur.
Woher erkennt Elm, welche Typen removeRight
erwartet? Aus dem Zusammenhang.
Betrachten wir den Quellcode con removeRight
:
removeRight word k =
String.left (String.length word - k) word
String.left
ist eine "eingebaute" Funktion. Elm kennt sie also bereits. Welchen Typ
hat sie? Schauen wir nach:
String.left
<function> : Int -> String -> String
Das zweite Argument von String.left
muss also ein Ausdruck vom Typ
String
sein.
Daraus kann Elm schon einmal schließen, dass word
vom Typ String
sein
muss.
Des Weiteren erwartet String.length
einen Input-Argument vom Typ
String
:
String.length
<function> : String -> Int
und liefert, wie wir sehen, ein Int
zurück. Der Ausdruck
String.length wort
"stimmt" also von den Typen her schon mal:
String.length
will einen String
, und wort
ist ein
String
. Der Wert des Ausdrucks String.length wort
ist also ein
Int
. Damit der Ausdruck
String.length word - k
legitim ist, muss nun k
auch ein String sein.
Elm
hat also die Signatur von removeRight
richtig hergeleitet.
Typ-Inferenz
Den Prozess, die Signatur (also die Typen der Eingabeparameter und des Rückgabewertes) aus dem Kontext herzuleiten, nennt man Type inference. Das geht gut, solange Sie keinen Fehler machen. Wenn Sie aber Fehler machen, dann scheitert die Typ-Inferenz, und Elm muss erraten, wo der Fehler liegt. Ändern Sie den Code vonremoveRight
in der Datei Session1.elm
wie folgt:
removeRight word k = String.left word (String.length word - k)So etwas geschieht häufig: Sie haben vergessen, ob
String.left
zuerst das Wort will
oder zuerst die Länge. Gehen Sie ins Repl-Fenster und tippen ein:
import Session1 exposing (..)
In der darauffolgenden langen Fehlermeldung sehen Sie unter anderen:
The 1st argument to `length` is not what I expect: 13| String.left word (String.length word - k) ^^^^ This `word` value is a: Int But `length` needs the 1st argument to be:
Warum glaubt Elm, dass word
ein Int
ist? Nun ja, wir haben es ja als
erstes Argument an String.left
übergeben (da wir die Argumente in falscher
Reihenfolge übergeben haben).
Elm glaubt also nun, wir wollten, dass word
ein Int
ist. Und dann
macht
String.length word
natürlich keinen Sinn mehr. Elm hat keine Möglichkeit, aus dem
Namen des Parameters, nämlich word, abzuleiten, was es sein soll.
Anstatt sich auf die Typ-Inferenz von Elm zu verlassen, sollten Sie immer die Signatur dazuschreiben. Wie das geht, sehen Sie hier.
Ohne Signatur
module Session1 exposing (..)
verb1 =
"kochen"
verb2 =
"baden"
removeRight word k =
String.left word (String.length word - k)
insPraeteritum word =
removeRight word 2 ++ "te"
Mit Signatur
module Session1 exposing (..)
verb1 : String
verb1 =
"kochen"
verb2 : String
verb2 =
"baden"
removeRight : String -> Int -> String
removeRight word k =
String.left (String.length word - k) word
insPraeteritum : String -> String
insPraeteritum word =
removeRight word 2 ++ "te"
Betrachten Sie im rechten Codebeispiel die Zeilen 4, 9, 14 und 19. Hierbei handelt es sich um die Signaturen der jeweiligen eingeführten Bezeichner.
Session1.elm
. Ändern Sie dann den
Code von
removeRight
wie zuvor, indem Sie im Körper die Argumente von
String.left
vertauschen, also:
Tippen Sie nunremoveRight : String -> Int -> String
removeRight word k =
String.left word (String.length word - k)
import Session1 exposing (..)
und betrachten die Fehlermeldung.
Todo: hier muss auch was über einfache Datentypen stehen: number, Int, Float, String, Bool