Softwerkskammer

 

Kapitel 2 - Zusammenfassung

Hier die Zusammenfassung des ersten Kapitels von "Real World Haskell" und ein paar Diskussionsergebnissen dazu:

Chapter 2. Types and Functions

Typen definieren welche Bits & Bytes wie verwendet werden sollen.

Strong Types

  • Es werde keine anders artigen Variablen Typen erlaubt (String statt Int -> Compilerfehler)
  • Es gibt keine automatische Konvertierung von Datentypen ( wie z.B. in C/C++ Int -> Float)
  • Binäre Datenklumpen können nicht auf den passenden Typ gecastet werden

Static Types

  • Es ist immer bekannt, welcher Wert welchen Datentyp hat
  • Haskells Kombination aus starken und statischen Typen erlauben ähnliche Praktiken wie Duck Typing
  • Triviale Fehler können nicht zur Laufzeit auftauchen

Type Inference

  • Haskell kann im Falle von fehlenden Typinformationen selbst erkennen welche Art von Typ vorliegt muss.
  • Man muss dem Compiler nicht alle Typen "mundgerecht" vorlegen.

Puzzelanalogy

In Haskell haben alle Datentypen (wie Puzzelteile) unterschiedliche Formen und Kompiler stellt sicher, dass diese
korrekt zusammen passen, während in dynamischen Programmiersprachen alle Daten einförmig (quasi 'quadratisch') vorliegen und somit immer zu passen scheinen. Doch dann muss via Tests noch mal extra geprüft werden ob ein korrektes Gesamtbild heraus kommt.

Function Application

Es werden Klammern nur für verschachtelte Funktionsaufrufe benötigt,
da Funktionen einen höheren Prezedenswert als operatoren haben.

Polymorphismus

Listen erwarten keinen konkreten Datentyp

ghci> :type []
[] :: [a]

'a' ist eine Typenvariable und steht stellvertretend für alle denkbaren
Datentypen, die in einer Liste abgelegt werden können. In einer Liste
kann nur ein Datentyp vorkommen.
Tupel erwarten auch keinen konkreten Datentyp und können obendrein verschiedene
Datentypen aufnehmen:

ghci> (1, 'a')
(1,'a')
ghci> :type it
it :: (Integer, Char)

Die Kombinationsmöglichkeit von Datentypen in Tupeln erlauben
das zusammenlegen zusammengehöriger Daten. Beispiel:

ghci> let books = [("Real World Haskell", 2010), ("Structure and Interpretation of Computer Programs", 1984)]
ghci> :type books
books :: [([Char], Integer)]

S. 25 Aufgabe 1

ghci> :type False
False :: Bool

ghci> :type (["foo", "bar"], 'a')
(["foo", "bar"], 'a') :: ( [[Char]], Char)

ghci> :type [(True, []), (False, [['a']])]
[(True, []), (False, [['a']])] :: [(Bool, [[Char]])]

Erst durch dem zweiten Eintrag in der Liste wird klar, dass das zweite Element des Tupels vom Typ Char sein muß.

Frage:

:type lines
lines :: String -> [String]

Warum kommt nicht lines :: [Char] -> [[Char]] heraus?
Wo ist String definiert? Wie kann ich es verwenden?

Antwort:

String ist ein Alias für [Char]. Er ist im Prelude mitdefiniert und kann
entsprechend sofort verwendet werden. Allerdings wird er nicht automatisch
zur TypeInferrence herangezogen.

Purity

Eine Funktion in Haskell hat per Default keine Seiteneffekte.
Das drückt sich auch in ihrem Typus aus.

Sollte eine Funktion doch in irgend einer Form das Gesamtsystem beeinflussen,
oder vom Status des Gesamtsystems abhängen, dann muss sich das in ihrem
Typus wider spiegeln.

Beispiel:

-- Seiteneffekt frei
ghci> :type lines
lines :: String -> [String]

-- Seiteneffekt behaftet
ghci> :type readFile
readFile :: IO String
-- Die IO-Monad (hä? Kommt erst noch...) im Typ deutet auf Seiteneffekte hin.
  • Aus der Typensignatur alleine lässt sich abgrenzen welche Auswirkungen eine Funktion hat und welche nicht.
  • Seiteneffektfreie Funktionen sind in sich schon modular abgegrenzt.
  • Die strikte Trennung von Seiteneffektfreiem und -behafteten Code macht die Weiterentwicklung einfacher.
  • Risiken die sich durch Abhängigkeiten zur Außenwelt ergeben, werden vermindert.

Lazy evaluation

In Haskell wird jede Expression erst zu einem 'thunk' ausgewertet, also einer
Art Platzhalter für das Ergebnis der Expression. Das Ergebnis selbst wird
erst berechnet, sobald der Wert konkret benötigt wird. (nonstrict evaluation)

Dadurch erklärt sich auch, warum wie der Typ des Ausdrucks 1 + 3 zustande kommt.

ghci> :type 1 + 3
1 + 3 :: Num a => a

Num a => sagt: es wird vorausgesetzt, dass es sich bei der Typvariablen a um
einen Typ oder Subtyp des Num-Typ handelt. D.h das Ergebnis des thunks muss vom Typ a
sein.

Zum Vergleich hier mal ein javascript snippet, dass die gleiche Bedeutung hat
wie haskells 1 + 3

function() {
    return 1 + 3;
}

Man kann sich also einen thunk ein bisschen wie eine Funktion vorstellen, die keine Parameter
erwartet und die noch nicht ausgeführt wurde. Haskell ist so schlau und weiß, was auch immer da
zurück kommen wird, es muss vom Typ Num sein. Der Wert selbst steht jedoch
erst fest, wenn der thunk ausgeführt wurde.

S 39. Aufgabe 1

Was kann last aufgrund seiner Typen-Signatur last :: [a] -> a tun und was
nicht?

  • Sie kann einen Wert aus der Liste extrahieren

  • Sie kann Listen nicht zu einem Wert des gleichen Types zusammenfassen (wie z.B. sum)

  • Sie kann nicht ihren Eingabe-Parameter ignorieren und einen beliebigen Wert des selben Types zurück geben.

  • Sie kann die Liste vom Typ a nicht in einen anderen Datentyp konvertieren

  • Sie kann die Liste nicht modifizieren

Frage:

Wieso kann man das so konkret sagen?

Antwort:

last erwartet eine Liste eines beliebigen
Typs a. Über diesen Typ a ist aber nichts weiter bekannt. Weder ob er von
irgend einem Basistyp wie Num abgeleitet ist noch ob er irgendwelche
Operatoren unterstützt. Daher kann last NUR einen Wert aus der Liste
extrahieren und nichts sonst.

S 39. Aufgaben 2 und 3

-- lastButOne.hs
lastButOne xs = head(drop(length(xs) -2) xs)

Wenn man diese Funktion dann im GHCi lädt:

ghci> :load lastButOne.hs
[1 of 1] Compiling Main             ( lastButOne.hs, interpreted )
Ok, modules loaded: Main.
ghci> lastButOne [1,2,3,4,5]
4
ghci> lastButOne [1,2]
1
ghci> lastButOne [1]
1
--Falsches Ergebnis
ghci> lastButOne []
*** Exception: Prelude.head: empty list
ghci>
Frage:

Wie soll die Funktion mit eine Liste umgehen, die weniger als zwei Elemenete hat?
Man kann nicht mal nil/null/undefined zurück geben? Selbst ein "Dummywert" wie -1 schließt sich aus, da
lastButOne mit jeder Art von typisierten Liste umgehen können muss.

Antwort:

Mit dem bisherigen Wissen sind mir keine bessren Lösungen eingefallen. Aber im Kapitel 3 werden Pattern-Matching und der Maybe-Basistyp eingeführt, die eine Lösung für dieses Problem darstellen können.