2. Elm
2.3 Funktionen definieren und aufrufen; einfache Datentypen
Alle Ausdrücke in einem Elm-Programm haben einen Typ:
wort = "angeln"wort : StringString.length wort6 : Intradius = 2.3radius : 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 = 13n : 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 : Stringverb1 ="kochen"verb2 : Stringverb2 ="baden"removeRight : String -> Int -> StringremoveRight word k =String.left (String.length word - k) wordinsPraeteritum : String -> StringinsPraeteritum 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 -> StringremoveRight 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