diff --git a/coaching/0-intro.md b/coaching/0-intro.md new file mode 100644 index 0000000000000000000000000000000000000000..d7b7cbb163225f3aad6dc2f6a3579eb5caa7bd35 --- /dev/null +++ b/coaching/0-intro.md @@ -0,0 +1,38 @@ +# 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!** diff --git a/coaching/1-1-lambda.md b/coaching/1-1-lambda.md new file mode 100644 index 0000000000000000000000000000000000000000..247735f5b96d90fd6814b733516d022d02d12ab8 --- /dev/null +++ b/coaching/1-1-lambda.md @@ -0,0 +1,115 @@ +# 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! diff --git a/coaching/1-2-types.hs b/coaching/1-2-types.hs new file mode 100644 index 0000000000000000000000000000000000000000..033643b3585acf35a616558de4104b6d7e0c064b --- /dev/null +++ b/coaching/1-2-types.hs @@ -0,0 +1,88 @@ +-- 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 diff --git a/coaching/1-3-functions.hs b/coaching/1-3-functions.hs new file mode 100644 index 0000000000000000000000000000000000000000..da1a2b0f371effd529e05ebb1fa11ccfcd6d23ed --- /dev/null +++ b/coaching/1-3-functions.hs @@ -0,0 +1,46 @@ +-- 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)) diff --git a/coaching/1-X-exercises.md b/coaching/1-X-exercises.md new file mode 100644 index 0000000000000000000000000000000000000000..12ab73403b33a1795faf39f2a9f35cd0baf595fe --- /dev/null +++ b/coaching/1-X-exercises.md @@ -0,0 +1,34 @@ +# 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! diff --git a/coaching/1-Y-solutions.md b/coaching/1-Y-solutions.md new file mode 100644 index 0000000000000000000000000000000000000000..477634ef3cc0b1204fdd3c43144f8563349db5fd --- /dev/null +++ b/coaching/1-Y-solutions.md @@ -0,0 +1,158 @@ +# 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) +``` diff --git a/coaching/2-1-advanced-types.hs b/coaching/2-1-advanced-types.hs new file mode 100644 index 0000000000000000000000000000000000000000..cd2178680ef2bc2a02ca9ba1c3d92d0831fb9885 --- /dev/null +++ b/coaching/2-1-advanced-types.hs @@ -0,0 +1,49 @@ +-- 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 diff --git a/coaching/2-2-modelling.hs b/coaching/2-2-modelling.hs new file mode 100644 index 0000000000000000000000000000000000000000..bc1d1b1759fc121dee615269fe01659f10466965 --- /dev/null +++ b/coaching/2-2-modelling.hs @@ -0,0 +1,58 @@ +-- 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" diff --git a/coaching/2-3-fold.hs b/coaching/2-3-fold.hs new file mode 100644 index 0000000000000000000000000000000000000000..f6b5dd97e3dd71fbd3a464f75f6a227ad4fbed39 --- /dev/null +++ b/coaching/2-3-fold.hs @@ -0,0 +1,217 @@ +-- 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 +-} diff --git a/coaching/2-X-exercises.md b/coaching/2-X-exercises.md new file mode 100644 index 0000000000000000000000000000000000000000..c54d5d3ee0c0f639c0db8df367304111a02cc488 --- /dev/null +++ b/coaching/2-X-exercises.md @@ -0,0 +1,46 @@ +# 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. diff --git a/coaching/2-Y-solutions.md b/coaching/2-Y-solutions.md new file mode 100644 index 0000000000000000000000000000000000000000..d92bfb779b4c8139d0bb582743c663093b97b15e --- /dev/null +++ b/coaching/2-Y-solutions.md @@ -0,0 +1,152 @@ +# 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) +``` diff --git a/coaching/3-1-list-comprehension.hs b/coaching/3-1-list-comprehension.hs new file mode 100644 index 0000000000000000000000000000000000000000..60f8173f3a14e8231da97b0efbcab1b433ede404 --- /dev/null +++ b/coaching/3-1-list-comprehension.hs @@ -0,0 +1,11 @@ +-- 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] diff --git a/coaching/3-2-boxes.hs b/coaching/3-2-boxes.hs new file mode 100644 index 0000000000000000000000000000000000000000..96dd85e1eceb3ecc011ca51717d958bd201108a2 --- /dev/null +++ b/coaching/3-2-boxes.hs @@ -0,0 +1,121 @@ +-- 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 diff --git a/coaching/3-3-monads.hs b/coaching/3-3-monads.hs new file mode 100644 index 0000000000000000000000000000000000000000..fe1505dd27acdc732ed11d64411c8adc9cee879e --- /dev/null +++ b/coaching/3-3-monads.hs @@ -0,0 +1,111 @@ +-- 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 diff --git a/coaching/3-X-exercises.md b/coaching/3-X-exercises.md new file mode 100644 index 0000000000000000000000000000000000000000..918ee400129cdd3b395484af7a2d7ce4bc720ad9 --- /dev/null +++ b/coaching/3-X-exercises.md @@ -0,0 +1,128 @@ +# 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. diff --git a/coaching/3-Y-solutions.md b/coaching/3-Y-solutions.md new file mode 100644 index 0000000000000000000000000000000000000000..cc4560bcb9336d9098854bb9c939a674ae62a201 --- /dev/null +++ b/coaching/3-Y-solutions.md @@ -0,0 +1,228 @@ +# 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. diff --git a/coaching/4-outro.md b/coaching/4-outro.md new file mode 100644 index 0000000000000000000000000000000000000000..e7c6751a933fd3d1ad4d1be201eb22e7b8590546 --- /dev/null +++ b/coaching/4-outro.md @@ -0,0 +1,45 @@ +# 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