Hier folgt die Zusammenfassung der ersten Hälfte des fünften Kapitels von "Real World Haskell".
"a string"
12345
true
null
[1, "foobar", true, null]
{"a":[1, 2, 3], "testable": false}
data JValue = JString String
| JNumber Double
| JBool Bool
| JNull
| JObject [(String, JValue)]
| JArray [JValue]
deriving (Eq, Ord, Show)```
```haskell
ghci> :load SimpleJSON
[1 of 1] Compiling SimpleJSON ( SimpleJSON.hs, interpreted )
Ok, modules loaded: SimpleJSON.
ghci> JString "foo"
JString "foo"
ghci> JNumber 2.7
JNumber 2.7
ghci> :type JBool True
JBool True :: JValue```
---
## Pattern Matching zum Lesen von JSON
```haskell
getString :: JValue -> Maybe String
getString (JString s) = Just s
getString _ = Nothing```
```haskell
ghci> :reload
Ok, modules loaded: SimpleJSON.
ghci> getString (JString "hello")
Just "hello"
ghci> getString (JNumber 3)
Nothing```
---
## Weitere Zugriffsfunktionen
```haskell
getInt (JNumber n) = Just (truncate n)
getInt _ = Nothing
getDouble (JNumber n) = Just n
getDouble _ = Nothing
getBool (JBool b) = Just b
getBool _ = Nothing
getObject (JObject o) = Just o
getObject _ = Nothing
getArray (JArray a) = Just a
getArray _ = Nothing
isNull v = v == JNull```
---
## Tipps am Rande
* Reload von *.hs
```haskell
ghci> :load SimpleJSON.hs
[1 of 1] Compiling Main ( SimpleJSON.hs, interpreted )
Ok, modules loaded: Main.
ghci> :reload
Ok, modules loaded: Main.
ghci> truncate 5.8
5
ghci> :module +Data.Ratio
ghci> truncate (22 % 7)
3```
---
## Haskell Module
* je Quellcode-Datei eine Moduldefinition (am Anfang der Datei stehen)
```haskell
module SimpleJSON
(
JValue(..)
, getString
, getInt
, getDouble
, getBool
, getObject
, getArray
, isNull
) where
data JValue = JString String
| JNumber Double
...
module
ist reserviertes Wort, Modulname == Dateiname (ohne Endung)module ExportEverything where```
### Nichts exportieren (selten sinnvoll)
```haskell
module ExportNothing () where```
---
## Haskell Modul kompilieren
```haskell
ghc -c SimpleJSON.hs```
* -c: nur Objekt-Code
* ohne -c würde es fehlschlagen weil main Funktion fehlt (unter Windows kein Problem)
* SimpleJSON.hi: Interface-Datei, Informationen über exportierte Namen des Moduls
* SimpleJSON.o: Objekt-Datei, enthält Maschinencode
---
## Module importieren
```haskell
module Main where
import SimpleJSON
main = print (JObject [("foo", JNumber 1), ("bar", JBool False)])```
* Beispiel aus Buch funktioniert nicht: `module Main () where`
```haskell
Main.hs:1:1:
The main function `main' is not exported by module `Main'```
* import muss am Dateianfang in einem Block mit anderen import-Anweisungen stehen
* import `Modulname` importiert alle vom Modul exportierte Namen
---
## Linking - Ausführbare Dateien generieren
```haskell
ghc -o simple Main.hs
Linking simple.exe ...```
* bei Angabe von `SimpleJSON.o` kommen Fehler
```haskell
ghc -o simple Main.hs SimpleJSON.o
Linking simple.exe ...
SimpleJSON.o:fake:(.data+0x0): multiple definition of `__stginit_SimpleJSON'
...```
* eigentlich explizit alle Dateien übergeben, die in der EXE landen sollen
* Mischen von *.hs und *.o Dateien beim Linking
* ghc kompiliert nur wenn nötig
---
## JSON Daten ausgeben (einfache Variante)
```haskell
module PutJSON where
import Data.List (intercalate)
import SimpleJSON
renderJValue :: JValue -> String
renderJValue (JString s) = show s
renderJValue (JNumber n) = show n
renderJValue (JBool True) = "true"
renderJValue (JBool False) = "false"
renderJValue JNull = "null"
renderJValue (JObject o) = "{" ++ pairs o ++ "}"
where pairs [] = ""
pairs ps = intercalate ", " (map renderPair ps)
renderPair (k,v) = show k ++ ": " ++ renderJValue v
renderJValue (JArray a) = "[" ++ values a ++ "]"
where values [] = ""
values vs = intercalate ", " (map renderJValue vs)
putJValue :: JValue -> IO ()
putJValue v = putStrLn (renderJValue v)```
---
## Separieren von puren und nicht puren Code
* mächtiges und weit verbreitetes Vorgehen in Haskell
* renderJValue gibt einfach String zurück
* putJValue gibt String auf Konsole aus (I/O)
* erhöht Flexibilität (z. B. Einfügen von Kompression)
---
## Zweischneidiges Schwert Typ-Inferenz
* Typ-Inferenz vom Haskell-Compiler ist mächtig und nützlich
* aber Gefahr, sich zu sehr auf Compiler zu verlassen
* Typ-Informationen einfach weglassen
* den Compiler den Typ ermitteln lassen
* Compiler wird möglicherweise schlüssige und konsitente Lösung finden
* möglicherweise aber nicht, was Programmierer gemeint hat
* Fehlersuche schwierig, weil Auftreten ungleich Ursache
---
## Beispiel problematische Typ-Inferenz
```haskell
-- Typsignatur weggelassen, man denkt, es wird ein String zurückgeliefert
upcaseFirst (c:cs) = toUpper c -- Vergessen ":cs" anzuhängen
-- aber Kompiler inferiert String -> Char
-- Wiederverwendung in anderer Funktion
camelCase :: String -> String
camelCase xs = concat (map upcaseFirst (words xs))
-- Fehler irreführend, Meldung bei Verwendung von upcaseFirst
Couldn't match expected type `[Char]' against inferred type `Char'
Expected type: [Char] -> [Char]
Inferred type: [Char] -> Char
In the first argument of `map', namely `upcaseFirst'
In the first argument of `concat', namely
`(map upcaseFirst (words xs))'
Failed, modules loaded: none.```
* jede Typsignatur verringert Fehlentscheidungen des Typ-Inferenz-Mechanismus
* Typsignaturen auch für den Leser hilfreich
---
## Tipps Typdeklarationen
* man muss nicht jedes kleine Codefragement mit Typ-Deklaration versehen
* sinnvollerweise sollte es je Top-Level-Definition eine Typsignatur geben
* lieber am Anfang ein paar Typ-Signaturen mehr explizit hinschreiben
---
## Allgemeiner Rendering-Ansatz
* aktuelles JSON-Rendering genau zugeschnitten auf die vorhandenen Datentypen und die JSON Format-Konventionen
* Ausgabe ist nicht so gut lesbar
* Ziel: Rendering als generische Aufgabe
* wie kann man eine Bibliothek bauen, die Daten sinnvoll für verschiedenste Situationen rendern kann
* Ausgabe sollte menschenlesbar und durch Maschinen verarbeitbar sein
* solche Bibliotheken heißen Pretty Printers
* obwohl es schon diverse Haskell-Pretty-Printer-Libraries gibt, entwicklen wir eine eigene: *Prettify*
---
## Zunächst neuer JSON Renderer
* nutzt noch nicht implementierte Prettify-API
* Prettify definiert abstrakten Typ `Doc`
```haskell
-- file: ch05/PrettyJSON.hs
renderJValue :: JValue -> Doc
renderJValue (JBool True) = text "true"
renderJValue (JBool False) = text "false"
renderJValue JNull = text "null"
renderJValue (JNumber num) = double num
renderJValue (JString str) = string str```
* die Funktionen text, double und string wird Prettify definieren
---
## Haskell Entwicklungs-Tipps
* Code während des Schreibens immer wieder Komplieren
* liefert gewisse Sicherheit durch Haskells starke Typisierung und Type Inferenz
* für die Entwicklung eines Programmgerüsts auf Stellvertreter (Stubs/Placeholder) setzen
* wir brauchen Stubs für `Doc`, `text`, `double`, `string`
* werden erst durch Prettify zur Verfügung gestellt
* bis dahin würde der Code nicht kompilieren
```haskell
-- file: ch05/PrettyStub.hs
import SimpleJSON
data Doc = ToBeDefined
deriving (Show)
string :: String -> Doc
string str = undefined
text :: String -> Doc
text str = undefined
double :: Double -> Doc
double num = undefined```
---
## undefined
* spezieller Wert `undefined` hat den Typ `a`
* Typprüfung erfolgreich, aber bei Ausführung kracht es
```haskell
ghci> :type undefined
undefined :: a
ghci> undefined
*** Exception: Prelude.undefined
ghci> :type double
double :: Double -> Doc
ghci> double 3.14
*** Exception: Prelude.undefined```
* wir können den Code zwar nicht laufen lassen, aber die Typprüfung ist immer erfolgreich
---
## Pretty Print von einem String
* String in JSON ist eine Serie von Zeichen umhüllt von Anführungszeichen
```haskell
-- file: ch05/PrettyJSON.hs
string :: String -> Doc
string = enclose '"' '"' . hcat . map oneChar```
* `enclose` Funktion packt ein `Doc` Wert in öffende und schließende Zeichen ein
```haskell
-- file: ch05/PrettyJSON.hs
enclose :: Char -> Char -> Doc -> Doc
enclose left right x = char left <> x <> char right```
* `(<>)` Funktion hängt zwei `Doc`-Werte aneinander (wie `(++)`)
```haskell
-- file: ch05/PrettyStub.hs
(<>) :: Doc -> Doc -> Doc
a <> b = undefined
char
nicht erklärt (wandelt Character in Doc um)char :: Char -> Doc
char c = undefined```
* Funktion `hcat` verbindet mehrere `Doc` Werte zu einem (analog concat für Listen)
```haskell
-- file: ch05/PrettyStub.hs
hcat :: [Doc] -> Doc
hcat xs = undefined```
* Funktion `string` führt `oneChar` Funktion für jedes Zeichen im String aus
* verbindet alles und umschliesst das Ergebnis in Anführungsstrichen
* oneChar rendert oder escapes einzelnes Zeichen
```haskell
-- file: ch05/PrettyJSON.hs
oneChar :: Char -> Doc
oneChar c = case lookup c simpleEscapes of
Just r -> text r
Nothing | mustEscape c -> hexEscape c
| otherwise -> char c
where mustEscape c = c < ' ' || c == '\x7f' || c > '\xff'
simpleEscapes :: [(Char, String)]
simpleEscapes = zipWith ch "\b\n\f\r\t\\\"/" "bnfrt\\\"/"
where ch a b = (a, ['\\',b])```
---
* `simpleEscapes` Wert ist eine Liste von Paaren
* genannt 'association list' oder 'alist'
* Verbindung zw. Zeichen und escaped Repräsentation
```haskell
ghci> take 4 simpleEscapes
[('\b',"\\b"),('\n',"\\n"),('\f',"\\f"),('\r',"\\r")]```
* Suche, ob Zeichen in `alist` enthalten ist, wird dann escaped
* nur druckbare ASCII-Zeichen werden unescaped ausgegeben
* Umwandlung eines Zeichens in Unicode-String `\u1234`
```haskell
-- file: ch05/PrettyJSON.hs
smallHex :: Int -> Doc
smallHex x = text "\\u"
<> text (replicate (4 - length h) '0')
<> text h
where h = showHex x ""```
---
* `showHex` Funktion kommt von Numeric Bibliothek (muss importiert werden)
```haskell
ghci> showHex 114111 ""
"1bdbf"```
* `replicate` Funktion wird von `Prelude` bereitgestellt und erzeugt ein Liste mit immer dem gleichen Element
```haskell
ghci> replicate 5 "foo"
["foo","foo","foo","foo","foo"]```
---
## Point-Free-Style
```haskell
-- file: ch05/PrettyJSON.hs
string :: String -> Doc
string = enclose '"' '"' . hcat . map oneChar```
* Stil für das Schreiben von Funktionsdefinitionen als Komposition von anderen Funktionen
* nichts mit dem '.' zu tun (für Funktionskomposition)
* 'Point' meint 'Value'
* point-free läßt die Values weg, auf denen operiert wird
* "pointy"-Version:
* Variable `s` die den Wert referenziert, auf dem gearbeitet wird
```haskell
-- file: ch05/PrettyJSON.hs
pointyString :: String -> Doc
pointyString s = enclose '"' '"' (hcat (map oneChar s))```