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 von removeRight 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.

Übungsaufgabe Kopieren Sie den rechten Code (mit Signaturen) in Session1.elm. Ändern Sie dann den Code von removeRight wie zuvor, indem Sie im Körper die Argumente von String.left vertauschen, also:
removeRight : String -> Int -> String
removeRight word k =
    String.left word (String.length word - k) 
Tippen Sie nun import Session1 exposing (..) und betrachten die Fehlermeldung.

Todo: hier muss auch was über einfache Datentypen stehen: number, Int, Float, String, Bool