Skip to content
Snippets Groups Projects
Commit d1048010 authored by Nicolas Lenz's avatar Nicolas Lenz :snowflake:
Browse files

Coaching files

parent 8d4a9f95
Branches
No related tags found
No related merge requests found
Showing with 1645 additions and 0 deletions
# Hallo!
## Kontakt
Matrix: @eisfunke:eisfunke.com / https://matrix.to/#/@eisfunke:eisfunke.com
EisfunkeLab-Chatgruppe auf Matrix: #lab:eisfunke.com / https://matrix.to/#/#lab:eisfunke.com
Mail: nicolas.lenz@udo.edu
Website: https://www.eisfunke.com
EisfunkeLab-Newsletter: https://lab.eisfunke.com
Bytegeschichten: https://bytegeschichten.eisfunke.com
Meine FuPro-Folien: https://git.eisfunke.com/lab/fupro (vielleicht hilfreich für die Grundlagen)
Alles auch nochmal auf:
https://events.eisfunke.com/lab/cfupro21
## Plan
Drei Themenblöcke mit je drei Themen:
- Lambda-Kalkül (Was ist das, Reduktion, Typinferenz)
- Datentypen: Wie funktioniert das, Listen, Strings, Tupel, Zahlen, Booleans, Maybe, Either und Typvariablen
- Rekursive Funktionen, Pattern Matching, Bedingungen, wichtige Funktionen
- Typklassen, Kinds, higher-order functions
- Modellierung mit Datentypen
- Faltungen und Church-Kodierung
- Listenkomprehensionen
- Funktoren, Applikative, Monaden, Plusmonaden, bind- und do-Notation
- Wichtige Monaden: IO, Liste, Leser, Schreiber, Zustand, Zustandstransformer
## Zeitplan
09:00 bis 12:00 Block 1 & 2
12:00 bis 13:00 Mittagspause
13:00 bis 15:00 Block 3
**Fragen? Gerne jederzeit!**
# 1.1: Lambda-Kalkül
Was ist das eigentlich?
Eine kleine Einführung im Podcast-Format gibt es hier:
https://bytegeschichten.eisfunke.com/2021/08/17/bg006-lambda-kalkuel/
(shameless plug)
## Definition
Die Definition von Lambda-Termen als Backus-Naur-Form:
Term ::= x | (Term Term) | λx . Term (x steht für beliebige Variable)
Beispiele:
- y
- λz . (x y)
- (λj . j j) z
~> z z
- (λx . x) y
~> y
## β-Reduktion
Regeln der Beta-Reduktion:
- (λx.P) Q -->β P[x:=Q]
- P -->β P' ==> P Q -->β P' Q
- Q -->β Q' ==> P Q -->β P Q'
- P -->β P' ==> λx.P -->β λx.P'
Bedeutet letztendlich: Argumente in Lambda-Funktionen einsetzen
Wichtig: Verschattung
- (λx.(λx.x)) y --->β λx.x
- (λx.(λx.x)) y -/->β λx.y
Wichtig: Umbenennung
- (λx.(λy.x y)) y --->β λy.y' y
- (λx.(λy.x y)) y -/->β λy.y y
## Typinferenz
Die Idee:
λx.x : c -> c
λy.z : a -> b
Die drei Regeln:
----------------------(var)
A ∪ {x : τ} |- x : τ
A |- M : σ → τ A |- N : σ
---------------------------------(app)
A |- (M N) : τ
A ∪ {x : σ} |- M : τ
----------------------(abs)
A |- λx.M : σ → τ
Beispiel-Typableitungsbaum für λs . λz . s (s z):
----------------------------------(var) ------------------------------(var)
{s : a -> a, z : a} |- s : a -> a {s : a -> a, z : a} |- z : a
----------------------------------(var) -------------------------------------------------------------------------(app)
{s : a -> a, z : a} |- s : a -> a {s : a -> a, z : a} |- (s z) : a
--------------------------------------------------------------------------------------------(app)
{s : a -> a, z : a} |- s (s z) : a
-----------------------------------------(abs)
{s : a -> a} |- \z . s (s z) : a -> a
-----------------------------------------(abs)
_ |- \s . \z . s (s z) : (a -> a) -> a -> a
Beachten: Es kann mehrere gültig ableitbare Typen für einen Term geben!
Z.B. \x.x : a -> a, aber auch \x.x : (a -> a) -> (a -> a)
Es gibt aber auch Terme und Kontexte, für die es keinen gültigen Typen gibt.
Beispiel: λx.x x
Tipp: Um Schreibarbeit zu sparen, Kontext getrennt definieren
Sei K = {s : a -> a, z : ???}
Zunächst Baum von unten aus aufbauen und die Typen einfach leer lassen
Welche Regel eingesetzt werden muss und wie die Terme aussehen, ist immer eindeutig!
...
-----------------(var) ---------------------(app)
K |- s : a -> a K |- s z : ???
----------------------------------------------------------(app)
K |- s (s z) : ???
------------------------------------(abs)
{s : a -> a} |- \z . s (s z) : ???
------------------------------------(abs)
_ |- \s . \z . s (s z) : ???
Wenn der Baum fertig aufgebaut ist, Typen erst einsetzen.
Geht ohne den Baum mehrfach zu schreiben und spart Zeit!
-- 1.2: Typen
x :: Int
x = 3
str :: String
str = "Hallo"
f :: Int -> Int
f = \x -> x * 2
g :: Int -> Int
g x = x * 3
-- {g : a -> a} |- λx . g x : a -> a
-- Funktionstypen, Currying
h :: Int -> (Int -> Int) -- = Int -> Int -> Int
h = \x -> \y -> x * y
-- h ist die Funktion, die einen Wert x nimmt und eine Funktion zurückgibt,
-- die einen Wert y nimmt und x * y zurückgibt
-- h 2 3 ~> 6
-- Geschachtelte Funktionen funktionieren also wie Funktionen mit mehreren Argumenten
-- h 3 = \y -> 3 * y
-- h 3 4 = (h 3) 4 = 3 * 4
-- h ist gecurryte Variante von h'
h' :: (Int, Int) -> Int
h'(x, y) = x * y
-- nimmt "wirklich" mehrere Argumente als Tupel
-- \x y -> bla ist übrigens nur Syntax für \x -> \y -> bla
-- Strings, Tupel, Zahlen, Booleans, Listen, Maybe, Either
bla :: (Int, String, Int)
bla = (3, "hallo", 5)
i :: Int -> (Int, Int)
i x = (x*2, x*3)
b :: Bool
b = False
xs :: [Int]
xs = [1,2,3]
ys :: [a]
ys = []
names :: [String]
names = ["hallo"]
mx :: Maybe Int
mx = Nothing
my :: Maybe Int
my = Just 3
division :: Int -> Int -> Maybe Int
division = undefined
-- undefined kann zum Testen genutzt werden um eine Funktion noch nicht
-- implementieren zu müssen, aber die Datei trotzdem kompilierbar zu haben
z :: Int
z = 3
ex, ey :: Either String Int
ex = Left "hallo"
ey = Right 3
division' :: Int -> Int -> Either String Int
division' = undefined
-- Lässt sich auch schachteln!
foo :: Either (Either (Either Int String) String) String
foo = Left (Left (Left 3))
-- Typvariablen
-- a kann jeder Typ sein, aber jeweils bei allen a's der selbe
myId :: a -> a
myId x = x
-- 1.3: Funktionen
-- Rekursive Funktionen schreiben
-- Bei Rekursion: Anfangen mit Rekursionabbruch, dem trivialen Fall, also dem Fall für
-- die leere Liste
-- Dann Rekursionsschritt
f :: [Int] -> [Int]
f [] = []
f (x:xs) = (2*x) : (f xs)
-- Pattern Matching funktioniert von oben nach unten
-- Versucht Eingabe in die Muster zu "matchen"
-- Weist dabei Variablen zu
g :: Maybe Int -> Int
g (Just 3) = 6
g (Just 5) = 17
g (Just x) = x + 3
g (Just 8) = 4 -- wird nie ausgeführt
g x = 89
h :: Int -> Int
h x = if x > 10 then x `div` 2 else x * 2
-- Guards funktionieren wie ein if/ein switch
-- Wichtig: sie prüfen Bedingungen (Bool-Werte), machen aber kein
-- Pattern Matching
i :: Int -> Int
i x
| x > 100 = x `div` 3
| x > 10 = x `div` 2
| otherwise = 3
foo :: Int -> Int -> Int
foo = (+)
-- Klammern sparen mit $
bar, bar' :: Int
bar = (+) 3 ( (+) 4 5 )
bar' = (+) 3 $ (+) 4 5
-- . ist die Verkettung/Nacheinanderausführung von rechts nach links
baz, baz' :: Int
baz' = (+3) . (+5) . (+8) $ 4
baz = (+3) ((+5) ((+8) 4))
# 1.X: Übungen zu Block 1
Beantwortet euch Fragen gegenseitig und diskutiert bei Uneinigkeit, was die richtige Antwort ist.
Antworten einzeln bearbeiten, helft euch gegenseitig bei Problemen und gleicht eure Lösungen ab.
1. Schreibt aus dem Kopf die drei Konstrukte des Lambda-Kalküls und ihre Namen auf. Was sind ihre Bedeutungen?
2. Reduziert folgenden Ausdruck:
`((λx. (λy. ((y (λx. (x y))) x) z) ) (x y)) (λx . x)`
3. Leitet den Typ dieses Ausdrucks im Kontext `Γ = {z : a}` her:
`λx.λy.x (y z)`
4. Wie muss der Typ `Int -> Int -> Int` geklammert werden?
5. Was ist der Unterschied zwischen den Typen `(Int -> Int) -> Int` und `Int -> (Int -> Int)`?
Was bedeuten diese Typen? Was könnten Funktionen mit diesen Typen sein?
6. Was bedeutet der Typ `() -> Int`? Was bedeutet der Typ `Int -> ()`?
7. Wo ist der Unterschied zwischen den Typen `(Int, Int) -> Int` und `Int -> Int -> Int`?
Kann man eine Funktion des einen Typens immer in eine des anderen umwandeln? Warum ja / warum nein?
8. Was ist der Unterschied zwischen `Just 3` und `3`? Ist das austauschbar verwendbar?
9. Stellt diesen Ausdruck nur mit Klammern, ohne `.` oder `$` dar:
`f . g $ x . y $ z`
10. Schreibt folgende Funktionen als rekursive Funktionen:
- `safeHead :: [a] -> Maybe a`
- `mapTwiceIfBiggerThanTen :: (Int -> Int) -> [Int] -> [Int]`
Wendet die Funktion auf alle Elemente der Liste zweimal an, außer auf Zahlen kleiner als 10, dann nur einmal
- `curry' :: ((a, b) -> c) -> (a -> b -> c)`
- Denkt euch selber eine Funktion mit den Grunddatentypen aus und implementiert sie!
# 1.X: Übungen zu Block 1
Beantwortet euch Fragen gegenseitig und diskutiert bei Uneinigkeit, was die richtige Antwort ist.
Antworten einzeln bearbeiten, helft euch gegenseitig bei Problemen und gleicht eure Lösungen ab.
1. Schreibt aus dem Kopf die drei Konstrukte des Lambda-Kalküls und ihre Namen auf. Was sind ihre Bedeutungen?
2. Reduziert folgenden Ausdruck:
`((λx. (λy. ((y (λx. (x y))) x) z) ) (x y)) (λx . x)`
```
= (λx. λy. y (λx. x y) x z ) (x y) (λx . x) (entklammert)
~> (λy. y (λx. x y) (x y') z ) (λx . x)
~> (λx . x) (λx. x (λx . x)) (x y') z
~> (λx. x (λx . x)) (x y') z
~> (x y') (λx . x) z
```
3. Leitet den Typ dieses Ausdrucks im Kontext `Γ = {z : a}` her:
`λx.λy.x (y z)`
Erst Baum aufbauen mit Lücken:
```
Sei K = {z : a, x : ???, y : ???}.
-------------- (var) ------------- (var)
K |- y : ??? K | z : ???
-------------- (var) ----------------------------------- (app)
K |- x : ??? K |- y z : ???
--------------------------------------------------- (app)
K |- x (y z) : ???
--------------------------------------------------- (abs)
{z : a, x : ???} |- λy . x (y z) : ???
---------------------------------------- (abs)
{z : a} |- λx . λy . x (y z) : ???
```
Bekannte Typen einsetzen:
```
Sei K = {z : a, x : ???, y : a -> ???}.
------------------- (var) ------------ (var)
K |- y : a -> ??? K |- z : a
-------------- (var) --------------------------------------- (app)
K |- x : ??? K |- y z : ???
--------------------------------------------------- (app)
K |- x (y z) : ???
--------------------------------------------------- (abs)
{z : a, x : ???} |- λy . x (y z) : ???
---------------------------------------- (abs)
{z : a} |- λx . λy . x (y z) : ???
```
Typen für Lücken nach und nach ausdenken:
```
Sei K = {z : a, x : b -> ???, y : a -> b}.
----------------- (var) ------------ (var)
K |- y : a -> b K |- z : a
------------------- (var) ------------------------------------- (app)
K |- x : b -> ??? K |- y z : b
------------------------------------------------------ (app)
K |- x (y z) : ???
--------------------------------------------------- (abs)
{z : a, x : ???} |- λy . x (y z) : ???
---------------------------------------- (abs)
{z : a} |- λx . λy . x (y z) : ???
```
Noch einen Typ ausdenken und zu Ende einsetzen:
```
Sei K = {z : a, x : b -> c, y : a -> b}.
----------------- (var) ------------ (var)
K |- y : a -> b K |- z : a
----------------- (var) ------------------------------------- (app)
K |- x : b -> c K |- y z : b
---------------------------------------------------------- (app)
K |- x (y z) : c
---------------------------------------------------------- (abs)
{z : a, x : b -> c} |- λy . x (y z) : (a -> b) -> c
---------------------------------------------------------- (abs)
{z : a} |- λx . λy . x (y z) : (b -> c) -> (a -> b) -> c
```
Der inferierte Typ ist also: `λx . λy . x (y z) : (b -> c) -> (a -> b) -> c`
Oft ist es gar nicht nötig, sich Typen für Variablen auszudenken.
Setzt zuerst bekannte Typen ein!
4. Wie muss der Typ `Int -> Int -> Int` geklammert werden?
Funktionstypen sind rechtsassoziativ: `Int -> (Int -> Int)`.
5. Was ist der Unterschied zwischen den Typen `(Int -> Int) -> Int` und `Int -> (Int -> Int)`?
Was bedeuten diese Typen? Was könnten Funktionen mit diesen Typen sein?
`(Int -> Int) -> Int` sind Funktionen, die eine Funktion `Int -> Int` nehmen und ein `Int`
zurückgeben.
Z.B.: `applyToThree f = f 3` mit `applyToThree (+5) ~> 8`.
`Int -> (Int -> Int)` sind Funktionen mit zwei Int-Argumenten (gecurryt).
Z.B.: `add x y = x + y` mit `add 2 3 ~> 5`.
6. Was bedeutet der Typ `() -> Int`? Was bedeutet der Typ `Int -> ()`?
Eine Funktion `f :: () -> Int` kann nur mit dem einzig möglichen Wert mit dem Typen `()`
aufgerufen werden: `()`. Es entspricht damit einer Konstante. Man kann sich das auch als
null-stellige Funktion vorstellen.
(`() -> c`: nullstellig, `(a) -> c`: einstellig, `(a, b) -> c`: zweistellig, ...)
`Int -> ()` sind Funktionien die immer das selbe zurückgeben: `()`. Letztendlich also Funktionen
ohne Rückgabe mit dem Platzhalter-Rückgabetypen `()`.
7. Wo ist der Unterschied zwischen den Typen `(Int, Int) -> Int` und `Int -> Int -> Int`?
Kann man eine Funktion des einen Typens immer in eine des anderen umwandeln? Warum ja / warum nein?
Beides sind Funktionen mit zwei Ints als Eingabe, aber einmal realisiert durch
Funktionschachtelung und einmal durch ein Tupel.
Mit `curry`/`uncurry` kann man das ineinander umwandeln, da sie effektiv das gleiche darstellen.
8. Was ist der Unterschied zwischen `Just 3` und `3`? Ist das austauschbar verwendbar?
Das eine ist ein `Maybe Int`, das andere ein `Int`. Damit sind die Werte nicht austauschbar
verwendbar, weil sie verschiedene Typen haben.
9. Stellt diesen Ausdruck nur mit Klammern, ohne `.` oder `$` dar:
`f . g $ x . y $ z`
`f (g (x (y z))`
10. Schreibt folgende Funktionen als rekursive Funktionen:
- `safeHead :: [a] -> Maybe a`
- `mapTwiceIfBiggerThanTen :: (Int -> Int) -> [Int] -> [Int]`
Wendet die Funktion auf alle Elemente der Liste zweimal an, außer auf Zahlen kleiner als 10, dann nur einmal
- `curry' :: ((a, b) -> c) -> (a -> b -> c)`
(gibt es schon als `curry`, implementiert es selber nochmal!)
- Denkt euch selber eine Funktion mit den Grunddatentypen aus und implementiert sie!
```
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:xs) = Just x
mapTwiceIfBiggerThanTen :: (Int -> Int) -> [Int] -> [Int]
mapTwiceIfBiggerThanTen f [] = []
mapTwiceIfBiggerThanTen f (x:xs)
| x > 10 = f (f x) : mapTwiceIfBiggerThanTen f xs
| otherwise = f x : mapTwiceIfBiggerThanTen f xs
curry' :: ((a, b) -> c) -> (a -> b -> c)
curry' f x y = f (x, y)
```
-- 2.1: Fortgeschrittener Typkram
-- Typklassen
-- Nichts mit Klassen aus objektorientierten Sprachen zu tun
-- Eher wie ein Interface: Garantiert, dass bestimmte Funktionen zur Verfügung stehen
class Maybeable a where
intoMaybe :: a -> Maybe a
instance Maybeable Int where
intoMaybe 4 = Nothing
intoMaybe x = Just x
instance Maybeable [a] where
intoMaybe list = Just list
-- a muss Maybeable sein...
f :: Maybeable a => a -> Maybe a
f x = intoMaybe x -- ...deswegen kann ich intoMaybe dadrauf benutzen
-- Einige integrierte Typklassen lassen sich automatisch ableiten, wenn einem die automatische
-- Version reicht.
data Test = Bla | Blubb String deriving (Show, Eq)
-- Kinds
-- :k Int "Fertiger Typ"
-- :k Maybe "Unfertiger Typ", es fehlt ein Typargument
-- :k Maybe Int "Fertiger Typ"
-- :k Either "Unfertiger Typ", es fehlen zwei Typargumente
-- Nur fertige Typen können als Typen benutzt werden
g :: Either String Int -> Maybe Int
g = undefined
-- Higher-order functions
-- Funktionen können Argumente für Funktionen sein!
xs :: [Int]
xs = map (+3) [1,2,3]
-- map nachimplementieret
map' :: (Int -> Int) -> [Int] -> [Int]
map' f [] = []
map' f (x:xs) = (f x) : map' f xs
-- 2.2: Modellieren mit Datentypen
-- Datentypengrundlagen
-- Mehrere Konstruktoren: Alternative, ähnlich Enum
-- "Summentyp"
data Color = Red | Green | Blue
data Foo = Foo | Bar
-- Wichtig: Foo ist je nach Kontext Typ oder Konstruktor, das ist aber nicht das selbe
test :: Foo
test = Foo
x, y, z :: Color
x = Red
y = Green
z = Blue
-- Konstruktor mit Parametern: Attribute, ähnlich struct
-- "Produkttyp"
data Bike = Bike String Color Int
-- In der Form ähnlich zu Tupel:
-- (String, Color, Int)
-- Summen- und Produkttypen lassen sich kombinieren:
data User = NormalUser String String | PremiumUser String String Int
-- Teile und Herrsche
-- Beim Modellieren von Problemen mit Datentypen klein anfangen und erstmal einfache
-- Sachen in einfachen Typen modellieren
-- Vorhandene Datentypen nutzen (Liste), außer wenn ein eigener Datentyp besser lesbar ist
-- Z.B. ist Bike oben klarer als einfach das Tupel (String, Color, Int)
-- Records
data Client = Client String String Int deriving Show
-- An sich gleicher Datentyp, aber mit record syntax definiert
data Client' = Client'
{ getFirstName :: String
, getLastName :: String
, getAge :: Int
} deriving Show
-- Records können optional zur Definition verwendet werden
nicolas, nicolas' :: Client'
nicolas = Client' "Nicolas" "Lenz" 23
nicolas' = Client' {getFirstName = "Nicolas", getLastName = "Lenz", getAge = 23}
-- Oder zum "updaten" (gibt neuen Wert zurück mit dem entsprechenden Attribut angepasst)
newNicolas :: Client'
newNicolas = nicolas {getLastName = "Käsebrot"}
-- Primär sind die records aber Destruktoren: funktionieren wie getter
-- getLastName nicolas ~> "Lenz"
-- 2.3: Faltungen und Church-Kodierung
-- Datenstrukturen
{-
[1,2,3]
Hübsche Syntax für folgendes:
1:(2:(3:[]))
Das kann man als Baumstruktur darstellen:
:
/ \
1 :
/ \
2 :
/ \
3 []
-}
-- Funktion, die eine Liste aufsummiert
sum' :: [Int] -> Int
sum' [] = 0
sum' (x:xs) = x + sum' xs
{-
Was passiert bei sum [1,2,3]?
1 : (2 : (3 : []))
:
/ \
1 :
/ \
2 :
/ \
3 []
wird zu
1 + (2 + (3 + 0))
+
/ \
1 +
/ \
2 +
/ \
3 0
... und das zusammengezogen ergibt 6, die Summe.
-> Die Funktion auf der Liste ersetzt eigentlich nur!
Alle : durch +
und alle [] durch 0
Das kann man als Faltung darstellen!
-}
-- setzt für : immer + ein und für [] 0
sum'' :: [Int] -> Int
sum'' = foldr (+) 0
-- Alternativ kann man sich die Faltung vorstellen als auffalten der Liste von rechts
-- next ist immer das nächste Element, acc (Akkumulator) das bisherige Zwischenergebnis
sum''' :: [Int] -> Int
sum''' = foldr (\next acc -> next + acc) 0
-- Macht das gleiche, zwei Richtungen um es gedanklich anzugehen
-- Versucht beides nachzuvollziehen um zu verstehen, was passiert
-- foldl faltet von links, bzw. es "dreht" den Baum
-- Siehe: https://en.wikipedia.org/wiki/Fold_(higher-order_function)
-- foldl (\acc next -> ...) startwert
-- Faltungen funktionieren für alle Datentypen!
-- Beispieldatentyp aus https://doi.org/10.1145%2F3122955.3122956
-- Was er bedeutet, ist aber egal für uns jetzt
data Graph a
= Empty
| Vertex a
| Overlay (Graph a) (Graph a)
| Connect (Graph a) (Graph a)
-- Faltungsfunktion für Graph
-- Ein Argument pro Konstruktor ("Interpretation"), und eines für den Wert selber
-- foldGraph :: () -> () -> () -> () -> Graph a -> b
foldGraph :: b -> (a -> b) -> (b -> b -> b) -> (b -> b -> b) -> Graph a -> b
-- dann immer die Interpretationen als Argumente annehmen
-- Pro Konstruktor ein Fall, den Konstruktor pattern matchen
-- Dann eigentlich nur die Interpretation einsetzen!
-- (siehe oben bei Listen, falten ist nur einsetzen!)
-- Aber bei rekursiven Attributen im Konstruktor rekursiv falten
foldGraph empty vertex overlay connect (Empty) = empty
foldGraph empty vertex overlay connect (Vertex x) = vertex x
foldGraph empty vertex overlay connect (Overlay g1 g2)
= overlay
(foldGraph empty vertex overlay connect g1)
(foldGraph empty vertex overlay connect g2)
foldGraph empty vertex overlay connect (Connect g1 g2)
= connect
(foldGraph empty vertex overlay connect g1)
(foldGraph empty vertex overlay connect g2)
-- Wie war das bei Listen?
-- Probieren mit diesem Typen
-- Äquivalent zum integrierten Haskell-Listentyp:
data List a = Nil | Cons a (List a) deriving Show
foldList :: b -> (a -> b -> b) -> List a -> b
foldList nil cons (Nil) = nil
foldList nil cons (Cons x xs) = cons x (foldList nil cons xs)
-- Church-Kodierungen
{-
Im Lambda-Kalkül gibt es keine Zahlen, Listen und Datentypen.
Wie Werte darstellen?
Idee: Wir stellen einen Wert dar als Funktion, die ihn faltet
Dafür müssen wir den Wert niemals konkret niederschreiben! Das geht also auch im reinen
Lambda-Kalkül.
-}
-- [1,2,3] als Haskell-Liste
eins2dreiH :: [Int]
eins2dreiH = 1 : (2 : (3 : []))
-- faltet die Liste [1,2,3] mit foldr
eins2dreiHC :: (Int -> b -> b) -> b -> b
eins2dreiHC cons nil = foldr cons nil [1,2,3]
-- faltet auch die Liste [1,2,3], aber die Liste wird nie konkret aufgeschrieben
-- Quasi fold ausgerechnet
eins2dreiC :: (Int -> b -> b) -> b -> b
eins2dreiC cons nil = cons 1 (cons 2 (cons 3 nil))
-- Das kann ich nun also auch im reinen Lambda-Kalkül darstellen!
-- (bis auf die Zahlen, die wir der Einfachheit jetzt nicht auch church-kodieren)
eins2drei :: (Int -> b -> b) -> b -> b
eins2drei = \c n -> c 1 (c 2 (c 3 n))
-- λc. λn. c 1 (c 2 (c 3 n))
-- Und das ist die Church-Kodierung für die Liste [1,2,3]
-- Damit kann ich dann weiterrechnen wie mit einer Haskell-Liste, aber im reinen Lambda-Kalkül
-- Ich kann also ganz normale Listenfunktionen schreiben, aber nur mit Lambda-Termen
-- Church-Kodierungen ermöglicht also Listen darzustellen im reinen Lambda-Kalkül
-- Konkateniert zwei Haskell-Listen mit fold
concatF :: [Int] -> [Int] -> [Int]
-- concatF l1 l2 = l1 ++ l2
concatF l1 l2 = foldr (:) l2 l1
-- Parallel dazu: Konkateniert zwei church-kodierte Listen
-- foldr ... ... l1 entspricht l1 ... ...
-- denn l1 ist ja church-kodiert und damit die Funktion, die l1 faltet
-- (:) ist church-kodiert c (cons)
concatC :: ((Int -> b -> b) -> b -> b) -> ((Int -> b -> b) -> b -> b) -> ((Int -> b -> b) -> b -> b)
concatC = \l1 l2 -> \c n -> l1 c (l2 c n)
{-
Probieren wir nochmal die Church-Kodierung mit dem Falten aus:
eins2drei: Funktion mit Argumenten c und n, gibt zurück:
eins2drei c n:
c
/ \
1 c
/ \
2 c
/ \
3 n
Oh!
Setzen wir (:) für c und [] für n ein!
eins2drei (:) [] ~>
:
/ \
1 :
/ \
2 :
/ \
3 []
Innerhalb von Haskell lässt sich die Church-kodierte Liste so also wieder in eine Haskell-Liste
übersetzen: wir setzen die Konstruktoren ein!
Haskell-interne Liste: foldr (+) 0 (1:(2:(3:[]))) ~> 6
Lambda-kodierte Liste: eins2drei (+) 0
+
/ \
1 +
/ \
2 +
/ \
3 0
Im Lambda-Kalkül gibt es nur Funktionen, Variablen, Funktionsanwendungen.
Wie Liste darstellen?
=> Mit Funktion, die die "gedachte" Liste auswertet!
Lambda-Church-Encoding stellt Datenstruktur quasi als ihre Faltungsfunktion dar
In Haskell haben wir konkrete Listendatenstrukturen, eben die Haskell-Listen, durch einsetzen der Konstruktoren in die Lambda-kodierte Liste können wir
aus der abstrakten "gedachte Liste dargestellt als Funktion die sie faltet" eine konkrete Haskell-Datenstruktur machen.
Tipp: Das geht für alle Datenstrukturen, die sich als so ein rekursive Baumstruktur mit verschiedenen Knotenarten darstellen lassen
-}
# 2.X: Übungen zu Block 2
1. Erstellt eine Typklasse `Doubleable` für Typen, die verdoppelt werden können.
2. Instanziiert `Doubleable` für `Int` und `[a]`.
3. Modelliert folgendes Szenario mit passenden Haskell-Datentypen:
Ein Geschäft besteht aus Abteilungen.
Jede Abteilung hat eine Manager'in und Mitarbeiter'innen,
die jeweils einen Namen und eine Adresse haben.
In den Abteilungen werden Produkte verkauft, die einen Namen und einen Preis haben.
Außerdem soll gespeichert werden, ob auf ein Produkt der normale oder der reduzierte
Mehrwertsteuersatz erhoben wird.
Verwendet deriving um die Typen anzeig- und vergleichbar zu machen.
Vergleicht eure Lösungen und diskutiert, was die sinnvollste Modellierung ist.
4. Schreibt eine Funktion, die alle Preise des Geschäfts um 10\% erhöht.
Gegeben sei folgender Datentyp:
`data Something a = Foo a | Bar Bool (Something a) | Baz (Something a) (Something a)`
5. Welchen Kind hat `Something`?
6. Erstellt zwei nicht-triviale Beispielinstanzen von `Something Int` und `Something String`.
7. Instanziiert die Typklassen `Show` und `Eq` für `Something` manuell.
8. Schreibt die Faltungsfunktion `foldSomething`. Beginnt mit der Typdeklaration.
9. Verwendet `foldSomething` um eine Funktion zu schreiben, die alle `Int`s in `Foo`s in einem
`Something Int` aufaddiert.
Schreibt die selbe Funktion auch nochmal als rekursive Funktion mit Pattern Matching.
Vergleicht die Implementationen und überlegt, wie sie zusammenhängen und wie sie sich ineinander
übersetzen lassen
10. Erstellt die Church-Kodierung eurer `Something`-Beispielinstanzen als Haskell-Lambda-Ausdrücke.
Lasst der Einfachheit halber die a's und Bools unkodiert.
11. Erstellt die Church-Kodierung der Konstruktoren `Foo`, `Bar` und `Baz` von Something.
# 2.X: Übungen zu Block 2
1. Erstellt eine Typklasse `Doubleable` für Typen, die verdoppelt werden können.
```
class Doubleable a where
double :: a -> a
```
2. Instanziiert `Doubleable` für `Int` und `[a]`.
```
instance Doubleable Int where
double x = 2 * x
instance Doubleable [a] where
double str = str ++ str
```
3. Modelliert folgendes Szenario mit passenden Haskell-Datentypen:
Ein Geschäft besteht aus Abteilungen.
Jede Abteilung hat eine Manager'in und Mitarbeiter'innen,
die jeweils einen Namen und eine Adresse haben.
In den Abteilungen werden Produkte verkauft, die einen Namen und einen Preis haben.
Außerdem soll gespeichert werden, ob auf ein Produkt der normale oder der reduzierte
Mehrwertsteuersatz erhoben wird.
Verwendet deriving um die Typen anzeig- und vergleichbar zu machen.
Vergleicht eure Lösungen und diskutiert, was die sinnvollste Modellierung ist.
```
data TaxRate = Normal | Reduced
data Product = Product
{ productName :: String
, productPrice :: Int -- Cent, Fließkommazahlen vermeiden wenn möglich wegen Ungenauigkeit
}
data Person = Person
{ personName :: String
, personAddress :: String
}
data Section = Section
{ sectionManager :: Person
, sectionEmployees :: [Person]
, sectionProducts :: [Product]
}
data Store = Store
{ storeSections :: [Section]
}
```
4. Schreibt eine Funktion, die alle Preise des Geschäfts um 10\% erhöht.
```
increasePrice :: Store -> Store
increasePrice (Store sections) = Store $ map increasePriceSection sections where
increasePriceSection section
= section {sectionProducts = map increasePriceProduct (sectionProducts section)}
increasePriceProduct (Product name price) = Product name (floor $ fromIntegral price * 1.1)
```
Gegeben sei folgender Datentyp:
`data Something a = Foo a | Bar Bool (Something a) | Baz (Something a) (Something a)`
5. Welchen Kind hat `Something`?
`* -> *`
6. Erstellt zwei nicht-triviale Beispielinstanzen von `Something Int` und `Something String`.
```
something1 :: Something Int
something1 = Bar True (Foo 3)
something2 :: Something String
something2 = Baz (Foo "hello") (Foo "bye") :: Something String
```
7. Instanziiert die Typklassen `Show` und `Eq` für `Something` manuell.
```
instance Show a => Show (Something a) where
show (Foo x) = unwords ["Foo", show x]
show (Bar b s) = unwords ["Bar", show b, "("++ show s ++ ")"]
show (Baz s1 s2) = unwords ["Baz", "("++ show s1 ++ ")", "("++ show s2 ++ ")"]
```
8. Schreibt die Faltungsfunktion `foldSomething`. Beginnt mit der Typdeklaration.
```
foldSomething :: (a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> Something a -> b
foldSomething foo bar baz (Foo x) = foo x
foldSomething foo bar baz (Bar b s) = bar b (foldSomething foo bar baz s)
foldSomething foo bar baz (Baz s1 s2) = baz
(foldSomething foo bar baz s1)
(foldSomething foo bar baz s2)
```
9. Verwendet `foldSomething` um eine Funktion zu schreiben, die alle `Int`s in `Foo`s in einem
`Something Int` aufaddiert.
Schreibt die selbe Funktion auch nochmal als rekursive Funktion mit Pattern Matching.
Vergleicht die Implementationen und überlegt, wie sie zusammenhängen und wie sie sich ineinander
übersetzen lassen
```
sumSomething :: Something Int -> Int
sumSomething = foldSomething
(\x -> x)
(\b acc -> acc)
(\acc1 acc2 -> acc1 + acc2)
```
10. Erstellt die Church-Kodierung eurer `Something`-Beispielinstanzen als Haskell-Lambda-Ausdrücke.
Lasst der Einfachheit halber die a's und Bools unkodiert.
```
something1C :: (Int -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b
something1C = \foo bar baz -> bar True (foo 3)
something2C :: (String -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b
something2C = \foo bar baz -> baz (foo "hello") (foo "bye")
```
11. Erstellt die Church-Kodierung der Konstruktoren `Foo`, `Bar` und `Baz` von Something.
```
foo
:: a -- x
-> (a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b
foo = \x -> \foo bar baz -> foo x
bar
:: Bool -- b
-> ((a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b) -- s
-> (a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b
bar = \b s -> \foo bar baz -> bar b (s foo bar baz)
baz
:: ((a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b) -- s1
-> ((a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b) -- s2
-> (a -> b) -> (Bool -> b -> b) -> (b -> b -> b) -> b
baz = \s1 s2 -> \foo bar baz -> baz (s1 foo bar baz) (s1 foo bar baz)
```
-- 3.1: Listenkomprehension
-- [Rückgabe | Variable <- Generator, Bedingungen]
-- {(x,y,z) | x,y,z ∈ ℕ, 5x + 3y² + 10 = z}
sol :: [(Int, Int, Int)]
sol = [(x,y,z) | z <- [0..], x <- [0..z], y <- [0..z], (5*x + 3*y^2 + 10) == z]
-- Nur erster Generator darf unendlich sein, sonst wird der erste nie weitergezählt!
foo :: [Int]
foo = [x + y | x <- [0,1,2,3], y <- [0,1,2,3], x == y]
-- 3.2: Boxen
-- Die verschiedenen Arten an Typklassen, die man sich als "Boxen" vorstellen kann,
-- die zur Monade führen
import Control.Monad
-- Funktoren
-- Boxen, in denen man Funktionen mappen kann
x :: [Int]
x = fmap (+3) [1,2,3] -- für Listen ist fmap = map
y :: Maybe Int
y = fmap (+3) (Just 3)
y' :: Maybe Int
y' = (+3) <$> Just 3 -- <$> ist Infix-Version von fmap
-- Applikative
-- Funktionen in der Box kann man auf Werte in der Box anwenden
z :: [Int]
z = [(+1), (+2)] <*> [1,2,3]
foo :: Maybe Int
foo = Just (+1) <*> Just 5
-- Kann man auch nutzen, um mehrstellige Funktionen zu mappen
mapMultiple :: [Int]
mapMultiple = (+) <$> [1,2,3] <*> [4,5,6]
-- Monaden
-- Boxen, die man auspacken kann und mit dem ausgepackten arbeiten kann, sofern man es anschließend
-- wieder einpackt
-- Es wirkt magisch, aber letztendlich nur: >>= = concatMap
bar :: [Int]
bar = [1,2,3] >>= (\x -> [x -x])
bar' :: [Int]
bar' = concatMap (\x -> [x, -x]) [1,2,3]
-- return packt etwas möglichs einfach in die Monade ein
baz :: [Int]
baz = return 3
-- Komprehension, bind- und do-Notation lassen sich ineinander übersetzen, nur verschiedene
-- Syntaxen, selbe Bedeutung
test :: [(Int, Int, Int)]
test = [(x,y,z) | z <- [0..], x <- [0..z], y <- [0..z]]
testBind :: [(Int, Int, Int)]
testBind =
[0..] >>= \z ->
[0..z] >>= \x ->
[0..z] >>= \y ->
return (x,y,z)
testDo :: [(Int, Int, Int)]
testDo = do
z <- [0..]
x <- [0..z]
y <- [0..z]
return (x,y,z)
-- Möglicherweise Listenmonade besser verständlich wenn man die Parallele von der Listen-
-- komperehnsion bedenkt
-- Plusmonaden
-- Die haben auch noch mzero (null, neutrales Element) und mplus (Addition)
-- Für Listen ist mzero die leere Liste
bla :: [Int]
bla = mzero
-- Für Listen ist mplus die Konkatenation
blubb :: [Int]
blubb = [1,2,3] `mplus` [4,5,6]
-- Dank Plusmonaden funktionieren Bedinungen/guards
sol :: [(Int, Int, Int)]
sol = [(x,y,z) | z <- [0..], x <- [0..z], y <- [0..z], (5*x + 3*y^2 + 10) == z]
solDo :: [(Int, Int, Int)]
solDo = do
z <- [0..]
x <- [0..z]
y <- [0..z]
guard ((5*x + 3*y^2 + 10) == z)
return (x,y,z)
-- guard :: Bool -> [()]
-- guard gibt bei nicht erfüllter Bedingung mzero zurück
-- Bei Listen also []
-- Und wenn man in der Listenkomprehension irgendwo bla <- [] hat, geht das halt nicht weiter!
-- Bei erfüllter Bedingung [()], und bla <- [()] macht alles nachfolgende einfach einmal, ändert
-- also nichts.
blabbDo = do
_ <- []
return 3
-- Zuweisungen können ausgelassen werden:
-- _ <- bla entspricht bla
-- bla >>= \_ -> blubb entspricht bla >> blubb
blabbBind :: [Int]
blabbBind = [1,2,3] >> return 3
-- 3.3: Wichtige Monaden
import Control.Monad
-- List (siehe 3.2)
-- Maybe (siehe auch 3.2)
-- Ermöglicht fehlerbehaftete Berechnungen zu kombinieren
-- Wenn man irgendwo Nothing entpackt, kommt insgesamt Nothing raus
-- safeDivide :: Int -> Int -> Maybe Int
x :: Maybe Int
x = do
x <- Just 3 -- safeDivide 9 3
y <- Nothing
return (x + y)
-- Either
-- Wie Maybe, aber Left ist eine Fehlermeldung
-- IO
-- IO Bla ist eine Ein-Ausgabe-Operation mit Rückgabetyp Bla
-- Z.B. getLine kann dann monadisch ausgepackt werden
main :: IO ()
main = do
name <- getLine
putStrLn $ "Hallo " ++ name
-- Leser
-- Ermöglicht es aus einem impliziten Kontext zu lesen
data Context = Foo String Int
test :: Context -> String
test (Foo s i) = s
bla :: Context -> Int
bla (Foo s i) = i
bar :: Int -> Context -> Int
bar i = do
t <- test
b <- bla
return (b+i)
baz :: Int -> Context -> Int
baz i (Foo t b) = b + i
-- Schreiber
-- Ermöglicht es in einen impliziten Kontext zu schreiben, z.B. Lognachrichten
logMsg :: String -> (String, ())
logMsg str = (str, ())
something :: Int -> (String, Int)
something n = do
logMsg "Ich logge was\n"
logMsg "Sicherheitshalber noch was loggen\n"
return (n*2)
-- Zustand
-- Ermöglicht es, aus einem impliziten Kontext zu lesen und zu schreiben
-- Impliziter Zustand!
newtype Zustand z a = Zs {ausf :: z -> (z, a)}
-- ausf ist Zustandsübergangsfunktion:
-- nimmt den alten Zustand und gibt den neuen Zustand und eine Rückgabe zurück
instance Functor (Zustand z) where
fmap f zs = Zs (\s -> let (s', a) = ausf zs s in (s', f a))
instance Applicative (Zustand z) where
pure = return
zf <*> za = zf >>= \f -> za >>= \a -> return (f a)
instance Monad (Zustand z) where
return x = Zs (\ s -> (s, x))
(Zs p) >>= f =
Zs (\s -> let {(s', y) = p s; (Zs q) = f y} in q s')
-- Zustand ist ein Int, diese Funktion gettet den Zustand und hat daher
-- den monadischen Rückgabetypen Int
getZustand :: Zustand Int Int
getZustand = Zs $ \z -> (z, z)
-- Zustand ist ein Int, diese Funktion settet nur und hat daher nur ()
-- als Platzhalter als monadischen Rückgabetypen
setZustand :: Int -> Zustand Int ()
setZustand newZ = Zs $ \z -> (newZ, ())
-- getZustand >>= \x -> ...
-- Kann man kombinieren
blablubb :: Zustand Int Int
blablubb = do
x <- getZustand
setZustand 6
setZustand (x + 1)
return 7
-- Zustandstransformer ist hauptsächlich wie Zustand, kann aber failen weil es Maybe
-- in der Zustandsübergangsfunktion hat
# 3.X: Übungen zu Block 3
```
import Control.Monad -- für guard
```
1. Definiert die unendliche Liste aller Tripel `(x,y,z)` mit `1*x = 2*y = 3*z`. Verwendet eine
Listenkomprehension. Stelle sie dann auch in der do- und bind-Notation dar.
2. Schreibe eine Funktion `[Int] -> [Int]`, die alle Elemente der Eingabeliste verdoppelt.
Schreibe die Funktion einmal mit einer Listenkomprehension und einmal mit map.
3. Schreibe eine Funktion `[Int] -> [Int]`, die nur die Elemente der Eingabeliste, die größer als 10
sind, beibehält — einmal als Listenkomprehension, einmal mit filter.
Gegeben sei folgender Datentyp:
`data List a = Nil | Cons a (List a)`
4. Erstelle eine Beispielinstanz von List Int.
5. Instanziiere Functor für List.
6. Instanziiere Applicative für List.
7. Instanziiere Monad für List.
8. Instanziiere MonadPlus für List.
9. Verwendet fmap/<$> um die Funktion (*2) auf eure Beispielliste zu mappen.
10. Verwendet <*> um eine Liste von Funktionen auf eure Beispielliste zu applizieren
11. Verwende <$> und <*> um `(+)` auf zwei Listen zu applizieren.
12. Verwendet die do-Notation, um die Listenkomprehension aus der ersten Aufgabe auf unseren
Listentypen zu übertragen. Warum benötigt es dafür die MonadPlus-Instanz?
MonadPlus wird benötigt, damit mzero zur Verfügung steht, das von guard benutzt wird um die
monadische Berechnung "abzubrechen".
13. Schreibt die Funktionen `safeLogarithm, safeDivide, safeSqrt :: Float -> Either String Float`,
die jeweils den natürlichen Logarithmus, die Quadratwurzel und den Kehrwert berechnen bzw.
eine sinnvolle Fehlermeldung ausgeben. Erstellt eine Funktion, die diese Funktionen nacheinander
auf einen Wert anwendet.
Macht das jeweils einmal mit Pattern Matching, der do-Notation und der bind-Notation.
Gegeben sei folgender Datentyp, der die Infos über den Aufruf einer Webseite enthält:
`data Context = Context {contextTime :: String, contextIP :: String, contextPath :: String}`
14. Schreibt eine Funktion String -> Context -> String, die den Eingabestring zweimal hintereinander
zurückgibt, wenn der Path "/double" ist, ansonsten den String unverändert lässt. Verwendet dabei
die Lesermonade.
Gegeben sei folgende Funktion:
```
collatz :: Int -> Int
collatz n
| even n = n `div` 2
| otherwise = 3*n + 1
```
(Das Collatz-Problem ist ein sehr spannendes ungelöstes Problem der Mathematik! Schaut euch mal
https://www.youtube.com/watch?v=094y1Z2wpJg an, wenn ihr Lust habt.)
15. Schreibe eine Funktion logMsg :: String -> (String, ()), die einen beliebigen String loggt.
16. Schreibe collatz in collatzLog :: Int -> (String, Int) um, das zusätzlich die vorgenommene
Operation loggt.
Gegeben sei die Zustandsmonade:
```
newtype Zustand z a = Zs {ausf :: z -> (z, a)}
instance Functor (Zustand z) where
fmap f zs = Zs (\s -> let (s', a) = ausf zs s in (s', f a))
instance Applicative (Zustand z) where
pure = return
zf <*> za = zf >>= \f -> za >>= \a -> return (f a)
instance Monad (Zustand z) where
return x = Zs ( \ s -> (s, x) )
(Zs p) >>= f =
Zs (\s -> let {(s', y) = p s; (Zs q) = f y} in q s')
```
17. Erklärt euch gegenseitig die Bedeutung von z und a.
Wir wollen nun mit einem Stack arbeiten: Der Zustand ist `[Int]`, das erste Element der Liste ist
das oberste Element auf dem Stack.
18. Schreibe die monadischen Funktionen `push` und `pop`, die ein Element auf den Stack pushen bzw.
das oberste Element entfernen und zurückgeben. `pop` sollte dafür monadisch ein Maybe Int zurück-
geben, damit Nothing zurückgegeben werden kann, falls der Stack leer ist.
19. Schreibe eine zustandsmonadische Funktion die das oberste Element des Stacks verdoppelt.
Gegeben sei die Zustandstransformer-Monade:
```
newtype ZustandsTransf s a = ZT {appZT :: s -> Maybe(s, a)}
instance Functor (ZustandsTransf s) where
fmap f (ZT g) = ZT (\s -> case g s of
Nothing -> Nothing
Just (s, y) -> Just(s, f y))
instance Applicative (ZustandsTransf s) where
pure = \x -> ZT (\s -> Just(s, x))
(ZT f) <*> (ZT g) = ZT(\s -> case f s of
Nothing -> Nothing
Just(s', h) -> case g s' of
Nothing -> Nothing
Just(s'', x) -> Just(s'', h x))
instance Monad (ZustandsTransf s) where
return x = ZT (\s -> Just(s, x))
(ZT p) >>= f = ZT (\s0 -> case p s0 of
Just(s1, x) -> let (ZT q) = f x in q s1
Nothing -> Nothing)
```
20. Erklärt euch den Unterschied zwischen Zustand und ZustandsTransf.
21. Schreibt eure zustandsmonadischen Funktionen für ZustandsTransf um.
# 3.X: Übungen zu Block 3
```
import Control.Monad -- für guard
```
1. Definiert die unendliche Liste aller Tripel `(x,y,z)` mit `1*x = 2*y = 3*z`. Verwendet eine
Listenkomprehension. Stelle sie dann auch in der do- und bind-Notation dar.
```
listL :: [(Int, Int, Int)]
listL = [(x,y,z) | z <- [0..], x <- [0..z], y <- [0..z], 1*x == 2*y, 2*y == 3*z]
listD :: [(Int, Int, Int)]
listD = do
z <- [0..]
x <- [0..z]
y <- [0..z]
guard $ 1*x == 2*y
guard $ 2*y == 3*z
return (x, y, z)
listB :: [(Int, Int, Int)]
listB =
[0..] >>= \z ->
[0..z] >>= \x ->
[0..z] >>= \y ->
guard (1*x == 2*y) >>
guard (2*y == 3*z) >>
return (x, y, z)
```
2. Schreibe eine Funktion `[Int] -> [Int]`, die alle Elemente der Eingabeliste verdoppelt.
Schreibe die Funktion einmal mit einer Listenkomprehension und einmal mit map.
```
doubleList, doubleList' :: [Int] -> [Int]
doubleList xs = [2*x | x <- xs]
doubleList' = map (2*)
```
3. Schreibe eine Funktion `[Int] -> [Int]`, die nur die Elemente der Eingabeliste, die größer als 10
sind, beibehält — einmal als Listenkomprehension, einmal mit filter.
```
greaterThan10, greaterThan10' :: [Int] -> [Int]
greaterThan10 xs = [x | x <- xs, x > 10]
greaterThan10' = filter (>10)
```
Gegeben sei folgender Datentyp:
`data List a = Nil | Cons a (List a)`
4. Erstelle eine Beispielinstanz von List Int.
```
exampleList :: List Int
exampleList = Cons 1 $ Cons 2 $ Cons 3 Nil
```
5. Instanziiere Functor für List.
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
6. Instanziiere Applicative für List.
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
7. Instanziiere Monad für List.
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
8. Instanziiere MonadPlus für List.
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
9. Verwendet fmap/<$> um die Funktion (*2) auf eure Beispielliste zu mappen.
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
10. Verwendet <*> um eine Liste von Funktionen auf eure Beispielliste zu applizieren
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
11. Verwende <$> und <*> um `(+)` auf zwei Listen zu applizieren.
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
12. Verwendet die do-Notation, um die Listenkomprehension aus der ersten Aufgabe auf unseren
Listentypen zu übertragen. Warum benötigt es dafür die MonadPlus-Instanz?
*Überprüft eure Implementation selber, indem ihr sie mit der eingebauten Haskell-Liste vergleicht.*
MonadPlus wird benötigt, damit mzero zur Verfügung steht, das von guard benutzt wird um die
monadische Berechnung "abzubrechen".
13. Schreibt die Funktionen `safeLogarithm, safeDivide, safeSqrt :: Float -> Either String Float`,
die jeweils den natürlichen Logarithmus, die Quadratwurzel und den Kehrwert berechnen bzw.
eine sinnvolle Fehlermeldung ausgeben. Erstellt eine Funktion, die diese Funktionen nacheinander
auf einen Wert anwendet.
Macht das jeweils einmal mit Pattern Matching, der do-Notation und der bind-Notation.
Gegeben sei folgender Datentyp, der die Infos über den Aufruf einer Webseite enthält:
`data Context = Context {contextTime :: String, contextIP :: String, contextPath :: String}`
14. Schreibt eine Funktion String -> Context -> String, die den Eingabestring zweimal hintereinander
zurückgibt, wenn der Path "/double" ist, ansonsten den String unverändert lässt. Verwendet dabei
die Lesermonade.
```
f :: String -> Context -> String
f input = do
path <- contextPath
if path == "/double"
then return (input ++ input)
else return input
```
Gegeben sei folgende Funktion:
```
collatz :: Int -> Int
collatz n
| even n = n `div` 2
| otherwise = 3*n + 1
```
(Das Collatz-Problem ist ein sehr spannendes ungelöstes Problem der Mathematik! Schaut euch mal
https://www.youtube.com/watch?v=094y1Z2wpJg an, wenn ihr Lust habt.)
15. Schreibe eine Funktion logMsg :: String -> (String, ()), die einen beliebigen String loggt.
```
logMsg :: String -> (String, ())
logMsg str = (str, ())
```
16. Schreibe collatz in collatzLog :: Int -> (String, Int) um, das zusätzlich die vorgenommene
Operation loggt.
```
collatzLog :: Int -> (String, Int)
collatzLog n
| even n = logMsg "even" >> return (n `div` 2)
| otherwise = logMsg "odd" >> return (3*n + 1)
```
Gegeben sei die Zustandsmonade:
```
newtype Zustand z a = Zs {ausf :: z -> (z, a)}
instance Functor (Zustand z) where
fmap f zs = Zs (\s -> let (s', a) = ausf zs s in (s', f a))
instance Applicative (Zustand z) where
pure = return
zf <*> za = zf >>= \f -> za >>= \a -> return (f a)
instance Monad (Zustand z) where
return x = Zs ( \ s -> (s, x) )
(Zs p) >>= f =
Zs (\s -> let {(s', y) = p s; (Zs q) = f y} in q s')
```
17. Erklärt euch gegenseitig die Bedeutung von z und a.
z ist der Typ des Zustandswertes, a ist der monadische Rückgabewert der Zustandsoperation.
Wir wollen mit einem Stack arbeiten: Der Zustand ist `[Int]`, das erste Element der Liste ist das
oberste Element auf dem Stack.
18. Schreibe die monadischen Funktionen `push` und `pop`, die ein Element auf den Stack pushen bzw.
das oberste Element entfernen und zurückgeben. `pop` sollte dafür monadisch ein Maybe Int zurück-
geben, damit Nothing zurückgegeben werden kann, falls der Stack leer ist.
```
push :: Int -> Zustand [Int] ()
push x = Zs $ \stack -> (x:stack, ())
pop :: Zustand [Int] (Maybe Int)
pop = Zs f where
f [] = ([], Nothing)
f (x:xs) = (xs, Just x)
```
19. Schreibe eine zustandsmonadische Funktion die das oberste Element des Stacks verdoppelt.
```
double :: Zustand [Int] ()
double = do
mx <- pop
case mx of
Nothing -> return ()
Just x -> do
push (x * 2)
```
Gegeben sei die Zustandstransformer-Monade:
```
newtype ZustandsTransf s a = ZT {appZT :: s -> Maybe(s, a)}
instance Functor (ZustandsTransf s) where
fmap f (ZT g) = ZT (\s -> case g s of
Nothing -> Nothing
Just (s, y) -> Just(s, f y))
instance Applicative (ZustandsTransf s) where
pure = \x -> ZT (\s -> Just(s, x))
(ZT f) <*> (ZT g) = ZT(\s -> case f s of
Nothing -> Nothing
Just(s', h) -> case g s' of
Nothing -> Nothing
Just(s'', x) -> Just(s'', h x))
instance Monad (ZustandsTransf s) where
return x = ZT (\s -> Just(s, x))
(ZT p) >>= f = ZT (\s0 -> case p s0 of
Just(s1, x) -> let (ZT q) = f x in q s1
Nothing -> Nothing)
```
20. Erklärt euch den Unterschied zwischen Zustand und ZustandsTransf.
21. Schreibt eure zustandsmonadischen Funktionen für ZustandsTransf um.
# Danke!
## Feedback
Ihr bekommt bald eine Mail mit einer Einladung zu einer anonymen Feedback-Umfrage.
Bitte nehmt teil! Vielen Dank!
Außerdem werde ich das Material veröffentlichen und euch den Link dazu schicken.
Im Anschluss werde ich eure persönlichen Daten von meinem Server löschen.
Weiteres Feedback ist auch sehr gerne willkommen! Hilfe ist auch gern gesehen.
Falls ihr Lust habt, schreibt mir gerne nach der Klausur mal, wie es gelaufen ist.
EisfunkeLab-Newsletter: lab.eisfunke.com
## Mich unterstützen
Mehr davon, und noch viel mehr? Z.B. Haskell in der Praxis?
Ihr könnt mir helfen mit Feedback, Werbung, Mitmachen oder natürlich mit...:
Überweisung: Nicolas Lenz, IBAN: DE46 4306 0967 1089 7106 00, BIC: GENODEM1GLS
(für mich gebührenfrei, nicht anonym, bevorzugt)
PayPal: https://paypal.me/eisfunke
(für mich gebührenfrei, nicht anonym)
LiberaPay: https://liberapay.com/Eisfunke/
(kostet mich Gebühren, anonym)
Vielen Dank!
## Kontakt
Matrix: @eisfunke:eisfunke.com / https://matrix.to/#/@eisfunke:eisfunke.com
EisfunkeLab-Chatgruppe auf Matrix: #lab:eisfunke.com / https://matrix.to/#/#lab:eisfunke.com
Mail: nicolas.lenz@udo.edu
Website: https://www.eisfunke.com
EisfunkeLab-Newsletter: https://lab.eisfunke.com
Bytegeschichten: https://bytegeschichten.eisfunke.com
Meine FuPro-Folien: https://git.eisfunke.com/lab/fupro
Alles auch nochmal auf:
https://events.eisfunke.com/lab/cfupro21
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment