Typkonstruktor

Werbung
Typkonstruktor-Klassen
Bisher haben wir Klassen eingesetzt um Funktionen für unterschiedliche Typen zu
überladen. Diese Idee lässt sich auf Typkonstruktoren fortsetzen. Wir haben zum Beispiel
zwei map-Funktionen kennen gelernt. Eine für Listen:
map :: (a -> b) -> [a] -> [b]
map _ []
= []
map f (x:xs) = f x : map f xs
und eine für Bäume:
data Tree a = Empty | Node (Tree a) a (Tree a)
mapTree :: (a -> b) -> Tree a -> Tree b
mapTree _ Empty = Empty
mapTree f (Node l x r) =
Node (mapTree f l) (f x) (mapTree f r)
Einstellige Typkonstruktoren, für die man eine solche map-Funktion definieren kann,
heißen Funktoren:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Die Variable f ist hier eine Typkonstruktor-Variable, d.h. sie abstrahiert von einstelligen
Typkonstruktoren.
Wir können nun folgende Functor-Instanzen angeben:
instance Functor [] where
fmap = map
instance Functor Tree where
fmap = mapTree
Auch für Maybe können wir eine Instanz angeben:
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just x) = Just (f x)
Die Instanz für den Typkonstruktor Maybe wendet die gegebene Funktion auf den
optionalen Wert an, wenn einer vorhanden ist.
Die Klasse Functor erlaubt es Funktionen wie die folgende zu schreiben, die man sowohl
auf Listen als auch auf Bäumen oder Maybe-Werten oder beliebigen anderen FunctorInstanzen anwenden kann:
1
inc :: Functor f => f Int -> f Int
inc = fmap (+1)
Auch IO ist ein einstelliger Typkonstruktor. Können wir für ihn auch eine Functor-Instanz
angeben? Ja:
instance Functor IO where
fmap f a = do x <- a
return (f x)
Die Functor-Klasse und die gezeigten Instanzen (bis auf die für Tree) sind in Haskell
vordefiniert, Sie können sie also verwenden, ohne sie selbst zu definieren.
Mit der Functor-Instanz für IO können wir das folgende Programm schreiben:
main = do x <- fmap length getLine
print x
Wenn wir es ausführen, müssen wir eine Zeile eingeben und bekommen dann deren Länge
angezeigt:
ghci> main
abc
3
Hier ein weiteres Beispiel, das zeigt, wie man den ersten Kommandozeilen-Parameter
ausgeben kann:
main = do x <- fmap head getArgs
print x
Speichert man dieses Programm in der Datei print-first-arg.hs, kann man es wie
folgt ausführen:
bash# runhaskell print-first-arg.hs 42 43 44
42
Instanzen der Klasse Functor müssen die folgenden sogenannten Funktor-Gesetze erfüllen:
fmap id
= id
fmap (f . g) = fmap f . fmap g
Das heißt, fmap ist ein Homomorphismus. Wir überprüfen die Gesetze beispielhaft für
die Maybe-Instanz:
2
fmap id Nothing = Nothing
= id Nothing
fmap id (Just x) = Just (id x) = id (Just x)
fmap (f . g) Nothing
= Nothing
= fmap f (fmap g Nothing)
= (fmap f . fmap g) Nothing
fmap (f . g) (Just x)
= Just ((f . g) x)
= Just (f (g x))
= fmap f (fmap g (Just x))
= (fmap f . fmap g) (Just x)
Für Listen und Bäume zeigt man die Gesetze per struktureller Induktion. Um zu zeigen,
dass die IO-Instanz die Funktor-Gesetze erfüllt, benötigt man Gesetze für die do-Notation,
die wir bisher noch nicht kennen.
3
Herunterladen