2. Elm

2.4 Fallunterscheidungen und Boolesche Werte

Die If-Then-Else-Konstruktion in Elm haben Sie ja bereits im Repl-Fenster kennengelernt. Für ein tieferes Verständnis brauchen wir allerdings boolesche Werte. Diese sind Wahrheitswerte, also entweder True oder False.

x = 2                        
x <= 3
True : Bool
x < 2
False : Bool
verb3 = "angeln"
String.right 2 verb3 == "ln"
True : Bool

Vergleichsoperatoren wie <, <=, >, >=, == geben Ihnen boolesche Werte. Es gibt auch vorgefertigte Funktionen, die Ihnen ja/nein-Antworten liefern, zum Beispiel String.endsWith:

String.endsWith "en" "kochen"                        
True : Bool

Beachten Sie die Signatur:

String.endsWith                        
<function> : String -> String -> Bool

Natürlich können wir auch selbst Funktionen schreiben, die einen booleschen Wert zurückliefern. Zum Beispiel eine Funktion, die überprüft, ob ihr Argument eine gerade Zahl ist. Hierfür benötigen wir die vorgefertigte Funktion modBy : Int -> Int -> Int, die den Rest nach der Division zurückgibt, beispielsweise

modBy 3 10                        
1
modBy 4 11
3

weil bei Division durch 3 die Zahl 10 einen Rest von 1 hinterlässt, und bei Division durch 4 die Zahl 11 einen Rest von 3. Eine Zahl ist nun gerade, wenn bei Division durch 2 ein Rest von 0 zurückbleibt. Daher:

isEven : Int -> Bool
isEven n =
    if modBy 2 n == 0 then
        True

    else 
        False

Es geht aber nur konziser. Der Vergleichsoperator == selbst liefert ja einen booleschen Wert zurück. Und in diesem Falle genau den, den wir wollen. Daher alternativ:

isEven : Int -> Bool
isEven n =
    modBy 2 n == 0

Boolesche Operatoren

Genau wie wir ganze Zahlen mit + oder * verknüpfen können, um neue ganze Zahlen zu erhalten, so gibt es auch für boolesche Werte Operatoren. Die üblichsten sind Und, Oder und Nicht. In Elm werden sie durch die Operatoren && für Und, || für Oder und not für Nicht repräsentiert.
x = 2                        
x >= 1 && x <= 3
True : Bool

Beachten Sie, dass Ausdrücke wie x >= 1 zu einem Wert vom Typ Bool auswerten und wir diesen Wert auch in einer Variable "abspeichern" können:

a = x <= 3
b = x < 0
a && b
False : Bool
a || b
True : Bool
Als weiteres Beispiel schreiben wir eine Funktion, die im Körper boolesche Operatoren verwendet. Sie testet, ob die Eingabezahl gerade und positiv ist:
isEvenAndPositive : Int -> Bool
isEvenAndPositive n =
    modBy 2 n == 0 && n > 0

diese hier testet, ob die Zahl gerade ist, aber nicht durch 4 teilbar:

isEvenButNotMultipleOfFour : Int -> Bool
isEvenButNotMultipleOfFour n =
    modBy 2 n == 0 && modBy 4 n /= 0

mit /= testen Sie Ungleichheit; die nächste Funktion testet, ob ein String mit "ern" oder "eln" endet:

endsInErnOrEln : String -> Bool
endsInErnOrEln word =
    String.right 3 word == "ern" || String.right 3 word == "eln"        
    
Übungsaufgabe

Schreiben Sie die Funktionen isEvenButNotMultipleOfFour und endsInErnOrEln ohne boolesche Operatoren && und ||, nur mit geschachtelten if-Konstruktionen.

Die Auswertung eines if-Ausdrucks

Wenn ein Ausdruck der Form
if condition then expression1 else expression2
ausgewertet werden soll, dann geschieht das in dieser Reihenfolge:
  1. es wird condition ausgewertet
  2. wenn condition zu True auswertet, dann wird expression1 ausgewertet und das ist dann auch der Wert des ganzen if-Ausdrucks
  3. wenn condition zu False auswertet, dann wird expression2 ausgewertet.

Merken Sie sich also, dass von expression1 und expression2 immer nur eines ausgewertet wird; dies ist insofern wichtig, als dass die Auswertung in der Realität oft viel Zeil beanspruchen kann. Es wird hier also nur der Ausdruck ausgewertet, der auch benötigt wird.

Übungsaufgabe Schreiben Sie eine Funktion isLeapYear : Int -> Bool, die überprüft, ob die gegebene (Jahres-)zahl ein Schaltjahr ist:
isLeapYear 2024                            
True
isLeapYear 1900
False

Tip. Falls Sie die genaue Regel für Schaltjahre nicht kennen, googeln Sie!

Übungsaufgabe Schreiben Sie eine Funktion nameOfWeekDay : Int -> String, die den Namen des Wochentags ausgibt:
nameOfWeekDay 2       
"Tuesday" : String
Was machen Sie, wenn die Zahl ungültig ist, beispielsweise 9?
Übungsaufgabe Schreiben Sie eine Funktion weekDayFromName : String -> Int, der den Namen des Wochentags erkennt:
weekDayFromName "Wednesday"                            
3

Wie behandeln Sie ungültige Eingaben, also zum Beispiel weekDayFromName "Thusday"?

Fallunterscheidungen mit case

Das mit den Wochentagen kriegen Sie eleganter mit einer sogenannten case-Konstruktion hin:
weekDayFromName : String -> Int
weekDayFromName day =
    case day of
        "Monday" ->
            1

        "Tuesday" ->
            2

        "Wednesday" ->
            3

        "Thursday" ->
            4

        "Friday" ->
            5

        "Saturday" ->
            6

        "Sunday" ->
            7

        _ ->
            -1

Eine case-Konstruktion lohnt sich immer dann, wenn Sie für einen Ausdruck (hier: day) verschiedene Werte abtesten. Beachten Sie die Zeile 75. Das _ entspricht dem else und wird aktiv, wenn keiner der vorherigen Fälle eingetreten ist. Meine Entscheidung, in diesem Falle -1 als "Fehlercode" zurückzugeben, gilt in Elm als ganz schlechter Stil. Ich erlaube es mir hier allerdings, weil wir noch nicht gelernt haben, wie man solche "Fehler" behandelt.

Übungsaufgabe Schreiben Sie mithilfe der case-Konstruktion eine Funktion nameOfWeekDay : Int -> String -> String, die den Namen des Wochentages in verschiedenen Sprachen ausgeben kann:
nameOfWeekDay 3 "English"                            
"Wednesday"
nameOfWeekDay 4 "Polish"
"Czwartek"