22717263bb
This reverts commit aab564e716
.
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")
|
||
|
||
```
|
||
|