172 lines
5.3 KiB
Markdown
172 lines
5.3 KiB
Markdown
|
Aufgabe 3
|
|||
|
=========
|
|||
|
|
|||
|
```haskell
|
|||
|
module Aufgabe3 where
|
|||
|
|
|||
|
import FunPB
|
|||
|
import DataPB
|
|||
|
import AreaCode
|
|||
|
import Data.Char
|
|||
|
```
|
|||
|
|
|||
|
`Functor` – ein Container?
|
|||
|
--------------------------
|
|||
|
|
|||
|
Zur Erinnerung:
|
|||
|
|
|||
|
```haskell
|
|||
|
class Functor f where
|
|||
|
fmap :: (a -> b) -> f a -> f b
|
|||
|
```
|
|||
|
|
|||
|
Aus der Vorlesung 2 kennen Sie bereits die `Functor`-Instanzen für `[]`, `Identity`
|
|||
|
und einen binären Baum. Außerdem haben Sie gelernt, dass Sie auch für Ihre eigenen
|
|||
|
Typen `Functor`-Instanzen definieren können. Eine Intuition dafür, ob sich für einen
|
|||
|
Typ eine `Functor`-Instanz schreiben lässt, erhalten Sie, indem Sie sich fragen, ob
|
|||
|
der Typ "eine Art Container" ist, auf deren Inhalte sich Funktionen (a -> b) anwenden
|
|||
|
lassen. Was bedeutet dies in Haskell-Syntax? Hierfür ist es dienlich, sich den
|
|||
|
`type constructor` des betreffenden Datentyps anzuschauen. Handelt es sich um einen
|
|||
|
polymorphen Datentyp stehen die Chancen gut, dass es ein `Functor` ist.
|
|||
|
|
|||
|
```haskell
|
|||
|
data Bool = False | True
|
|||
|
|
|||
|
data Maybe a = Nothing | Just a
|
|||
|
|
|||
|
data (,) a b = (a,b)
|
|||
|
```
|
|||
|
|
|||
|
`Bool` ist offenbar kein `Functor`, denn der Typkonstruktor `Bool` hat keine Parameter –
|
|||
|
hier ist nur Platz für True und False. `Maybe a` dagegen hat einen Parameter, einen freien
|
|||
|
Slot für alles mögliche. Der Tupeltyp `(,) a b` hat sogar zwei Parameter – er kann Dinge von
|
|||
|
zwei verschiedenen Typen enthalten. Hier stellt sich die Frage, für welchen Container die
|
|||
|
Functorinstanz definiert ist. Ähnlich wie Funktionen, lassen sich auch Typkonstruktoren
|
|||
|
partiell anwenden. Für die Instanziierung werden dem Typkonstruktor daher alle bis auf ein
|
|||
|
Parameter übergeben. Dieser letzte, freie Parameter legt dann den Inhalt des "Container"
|
|||
|
fest. Beispiel:
|
|||
|
|
|||
|
```haskell
|
|||
|
instance Functor ((,) a) where --Die Functor-Instanz ist für (,) a definiert
|
|||
|
fmap f (x,y) = (x, f y) --Also wird über Tupelslot 2 "gemapt"
|
|||
|
```
|
|||
|
|
|||
|
Implementieren Sie `Functor`-Instanzen für die folgenden Datentypen:
|
|||
|
|
|||
|
```haskell
|
|||
|
data Vielleicht a = Nichts | Etwas a
|
|||
|
deriving (Show,Eq)
|
|||
|
|
|||
|
instance Functor Vielleicht where
|
|||
|
fmap = undefined
|
|||
|
|
|||
|
|
|||
|
data Entweder a b = Jenes a | Dieses b
|
|||
|
deriving (Show,Eq)
|
|||
|
|
|||
|
instance Functor (Entweder a) where
|
|||
|
fmap = undefined
|
|||
|
|
|||
|
|
|||
|
data Konstant a b = Konstant a
|
|||
|
deriving (Show,Eq)
|
|||
|
|
|||
|
instance Functor (Konstant a) where
|
|||
|
fmap = undefined
|
|||
|
```
|
|||
|
|
|||
|
Achtung: Die "Container"-Metapher hat ihre Grenzen. Betrachten Sie hierzu noch einmal den
|
|||
|
Datentyp Pred a von Zettel 1: `data Pred a = Pred (\a -> Bool)`.
|
|||
|
|
|||
|
```haskell
|
|||
|
data Pred a = Pred (a -> Bool)
|
|||
|
runPred :: Pred a -> (a -> Bool)
|
|||
|
runPred (Pred p) = p
|
|||
|
```
|
|||
|
|
|||
|
`Pred a` macht den Anschein als handele es sich auch hier um einen Container mit Inhalt a.
|
|||
|
Trotzdem lässt sich `Functor` hierfür nicht instanziieren. Der Unterschied liegt darin,
|
|||
|
dass der Typaramter `a` als Input und nicht als Ergebnis in der vom Konstruktor `Pred`
|
|||
|
"eingepackten" Berechnung auftaucht. Allerdings lässt sich hier auf eine sehr ähnliche
|
|||
|
Eigenschaft abstrahieren, die wir für's Erste `InputFunctor` nennen wollen.
|
|||
|
|
|||
|
```haskell
|
|||
|
class InputFunctor f where
|
|||
|
inputmap :: (a -> b) -> f b -> f a
|
|||
|
```
|
|||
|
|
|||
|
Schreiben Sie eine `InputFunctor`-Instanz für `Pred a`.
|
|||
|
|
|||
|
```haskell
|
|||
|
instance InputFunctor Pred where
|
|||
|
inputmap = undefined
|
|||
|
```
|
|||
|
|
|||
|
Hiermit lässt sich nun bequem die folgende Funktion definieren, welche aus einem
|
|||
|
`Pred Int` ein `Pred String` macht, das prüft, ob ein Eingabestring wenigstens die Länge 5 hat.
|
|||
|
|
|||
|
```haskell
|
|||
|
atLeast5 :: Pred Int
|
|||
|
atLeast5 = Pred $ (\x -> x>=5)
|
|||
|
|
|||
|
atLeast5Char :: Pred String
|
|||
|
atLeast5Char = inputmap length atLeast5
|
|||
|
```
|
|||
|
|
|||
|
Functorial phone book
|
|||
|
---------------------
|
|||
|
|
|||
|
Jetzt noch einmal zurück zu PhoneBook aus Aufgabe 1.
|
|||
|
|
|||
|
```haskell
|
|||
|
type Number = String
|
|||
|
type Name = String
|
|||
|
type Entry = [Number]
|
|||
|
|
|||
|
newtype PhoneBook = PB (Name -> Entry)
|
|||
|
```
|
|||
|
|
|||
|
`PhoneBook` hat keinen Parameter, aber die allgemeinere Version `FunPB` hat sogar zwei:
|
|||
|
|
|||
|
```haskell
|
|||
|
data FunPB a b = FunPB (a -> (a,[b]))
|
|||
|
```
|
|||
|
|
|||
|
Beachten Sie, dass sich auch der Rückgabetyp ein wenig unterscheidet. Die Idee ist,
|
|||
|
dass FunPB zusätzlich zu den assoziierten `Number`s (angenommen `b` ist `Number`) auch
|
|||
|
den gesuchten `Name` (angenommen `a` ist `Name`) zurückgibt.
|
|||
|
|
|||
|
Implementieren Sie eine `Functor`-Instanz für `FunPB`.
|
|||
|
Hinweis: Sie können benutzen, dass für `[]`, `((,) a)` und sogar für den "function arrow"
|
|||
|
`((->) a)` bereits `Functor`-Instanzen in der [`GHC.Base`](https://hackage.haskell.org/package/base "GHC.Base") existieren.
|
|||
|
|
|||
|
```haskell
|
|||
|
instance Functor (FunPB a) where
|
|||
|
fmap = undefined
|
|||
|
```
|
|||
|
|
|||
|
Die Functor-Instanz erlaubt uns nun die Funktionen
|
|||
|
`separateAreaCode :: Number -> (AreaCode,Number)` und `snd` zu verwenden, um ein
|
|||
|
TelefonBuch mit separiertem bzw. ganz ohne AreaCode zu erhalten.
|
|||
|
|
|||
|
```haskell
|
|||
|
areaCodeFunPB :: FunPB Name Number -> FunPB Name (AreaCode,Number)
|
|||
|
areaCodeFunPB = fmap separateAreaCode
|
|||
|
|
|||
|
withoutAreaCodeFunPB :: FunPB Name Number -> FunPB Name Number
|
|||
|
withoutAreaCodeFunPB = fmap (snd.separateAreaCode)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```haskell
|
|||
|
result = "Suche \"Paula\" in (FunPB Name Number): \n"
|
|||
|
++ (show $ runFunPB (dataToFunPB simpleData) "Paula") ++ "\n"
|
|||
|
++ "Suche \"Paula\" in (FunPB Name (AreaCode,Number)): \n"
|
|||
|
++ (show $ runFunPB (areaCodeFunPB $ dataToFunPB simpleData) "Paula") ++ "\n"
|
|||
|
++ "Suche \"Paula\" in (FunPB Name Number) ohne Vorwahl: \n"
|
|||
|
++ (show $ runFunPB (withoutAreaCodeFunPB $ dataToFunPB simpleData) "Paula")
|
|||
|
|
|||
|
```
|
|||
|
|