Macros • Macros erlauben es die Sprache zu modifizieren • ... in potentiell radikaler Weise • ... ohne auf Rich Hickey warten zu müssen • Macros werden durch Homoikonizität viel einfacher! Montag, 3. Februar 14 Typsysteme • dynamic vs. static • schwach vs. stark • Bei statisch typisierten Spachen: implizit vs. explizit Montag, 3. Februar 14 Algebraische Datentypen Typkonstruktor Wertkonstruktoren Prelude> data MyVec a = My3Vec a a a | My2Vec a a | MyVec a | Kaboom Prelude> let a = My3Vec 'a' 'b' 'c' Prelude> :t a a :: MyVec Char Prelude> let b = My2Vec 1 2 Prelude> :t b b :: MyVec Integer Prelude> let c = MyVec "Wow" Prelude> :t c c :: MyVec [Char] Prelude> let d = Kaboom Prelude> :t d d :: MyVec a Prelude> let e = MyVec Kaboom Prelude> :t e e :: MyVec (MyVec a) Montag, 3. Februar 14 Maybe data Maybe a = Just a | Nothing mydiv :: Int -> Int -> Maybe Int mydiv a b | b == 0 = Nothing | otherwise = Just (a `div` b) *Main> mydiv 4 2 Just 2 *Main> mydiv 4 0 Nothing *Main> :t mydiv 4 3 mydiv 4 3 :: Maybe Int Montag, 3. Februar 14 List data [] a = [] | a : [a] data Liste a = Paar a (Liste a) | Leer deriving Show > Paar 1 (Paar 2 Leer) Paar 1 (Paar 2 Leer) > > (:) 1 ((:) 2 []) [1,2] Montag, 3. Februar 14 Typeclass • Typeclasses in Haskell entsprechen in etwa Protokollen in Clojure • Lösen ebenfalls das Expression Problem • Spezifizieren Funktionen auf einem Datentyp • In Haskell gibt es auch noch default Implementierungen Montag, 3. Februar 14 "Eq" in Clojure data MyVector a = MyVector a a class Eq (==) (/=) x == x /= a where :: a -> :: a -> y = not y = not a -> Bool a -> Bool (x /= y) (x == y) instance (Eq a) => Eq (Vector a) where ... Montag, 3. Februar 14 (defrecord MyVector [x y]) (defprotocol Eq (eq [this that])) (extend-protocol Eq MyVector (eq [this that] ...)) Clojure Alternativen • Ersatz für Static typing in Clojure? • Optional Typing mit core.typed • Prismatic Schemas • Contracts • Tests • Alles nur geeignet zur Fehlervermeidung, nicht zur Erkennung von Seiteneffekten Montag, 3. Februar 14 Currying Jede Funktion in Haskell nimmt genau einen Parameter Prelude> :t (+) (+) :: Num a => a -> a -> a Assoziativität ist Num a => a -> (a -> a) Eine n-stellige Funktion schreibt man als Funktion, die einen Parameter nimmt und eine n-1 stellige Funktion liefert Montag, 3. Februar 14 Currying • Allgemeines Currying ist in Clojure nicht direkt realisierbar • Grund: variable Anzahl an Argumenten • Was ist das Resultat von (+ 2 3)? • 5? • Eine Funktion, die +5 rechnet? Currying geht nur wenn wir die Anzahl der Parameter fixieren Montag, 3. Februar 14 Curry Macro (defmacro curry ([n f] `(curry ~n [] ~f)) ([n p f] (if (= 0 n) `(~f ~@p) (let [x (gensym)] `(fn [~x] (curry ~(dec n) ~(conj p x) ~f)))))) user=> (def +c2 (curry 2 +)) #'user/+c2 user=> (+c2 1) #<PLUS_c2$fn... PLUS_c2$fn...> user=> ((+c2 1) 4) 5 user=> (macroexpand-all '(curry 2 +)) (fn* ([G__3943] (fn* ([G__3944] (+ G__3943 G__3944))))) Montag, 3. Februar 14 Pattern Matching Montag, 3. Februar 14 Pattern Matching • Verwandt mit Destructuring in Clojure • und Unifikation in logischen Sprachen • Pattern matching ist Unifikation in eine Richtung • Mächtigkeit: Destructuring < Pattern Matching < Unification Montag, 3. Februar 14 Beispiele (defn summe ([[x & xs]] (if-not x 0 (+ x (summe xs))))) summe [] = 0 summe (x:xs) = x + summe xs summe([],0). summe([H|T],X) = summe(T,Y), X is Y + H. Montag, 3. Februar 14 => (summe [1 2 3]) 6 > summe [1,2,3] 6 ?- summe([1,2,3],R). R = 6? Beispiele factorial 0 = 1 factorial n = n * factorial (n-1) Nebenbei: Was passiert, wenn man n-1 nicht klammert? Montag, 3. Februar 14 Beispiele foo 0 = 1 foo n = 7 foo 2 = 6 *Main> :load bsp1 [1 of 1] Compiling Main *Main> foo 0 1 *Main> foo 1 7 *Main> foo 2 7 ( bsp1.hs, interpreted ) bsp1.hs:10:1: Warning: Pattern match(es) are overlapped In an equation for `foo': foo 2 = ... Ok, modules loaded: Main. Montag, 3. Februar 14 Algebraische Datentypen Pattern matching funktioniert auch mit algebraischen Datentypen data Shape = Rectangle Float Float Float Float | Circle Float Float Float surface (Circle _ _ r) = pi * r ^ 2 surface (Rectangle x1 y1 x2 y2) = (x2-x1) * (y2-y1) *Main> map surface [Circle 0 0 10, Rectangle 0 0 10 10, Circle 1 1 1] [314.15927,100.0,3.1415927] Montag, 3. Februar 14 Beispiele fst (a,_) = a snd (_,b) = b *Main> fst (1,2) 1 *Main> snd (1,2) 2 Montag, 3. Februar 14 Guards Guards sind wie cond auf oberster Ebene der Funktion max' a b | a > b = a | otherwise = b a `myCompare` b | a > b = GT | a == b = EQ | otherwise = LT Montag, 3. Februar 14 Pattern Matching in Clojure • In Clojure gibt es kein Pattern Matching • Pattern matching ist nicht simple • Es gibt aber eine Bibliothek core.match (defn fizzbuzz [n] (match [(mod n 3), (mod n 5)] [0 0] "FizzBuzz" [0 _] "Fizz" [_ 0] "Buzz" :else (str n))) user=> (map fizzbuzz (range 10)) ("FizzBuzz" "1" "2" "Fizz" "4" "Buzz" "Fizz" "7" "8" "Fizz") Montag, 3. Februar 14 Lazyness Montag, 3. Februar 14 Lazy Lists • Kennen wir schon aus Clojure • In Haskell gibt es aber keine speziellen Lazy Datenstrukturen • Unendliche Datenstrukturen sind ein Nebeneffekt der lazy Auswertung von Ausdrücken Montag, 3. Februar 14 Evaluation in Haskell Ausdrücke werden nur evaluiert, wenn sie benötigt werden fib 0 = 1 fib 1 = 1 fib n = fib (n-1) + fib (n-2) *Main> let y = fib 30 *Main> y (ca. 5 Sekunden) 1346269 Montag, 3. Februar 14 Evaluation in Haskell • Definition eines Ausdrucks erzeugt eine Art promise, dass der Wert ausgerechnet werden kann • In Haskell nennt man das thunk Montag, 3. Februar 14 Lazyness • Vorteile • Unendliche Datenstrukturen • Vermeidung unnötiger Evaluation • Nachteile • Schwieriger zu debuggen • Je nach Sichtweise:Vorteil oder Nachteil • Lazy Evaluation erzwingt Purity Montag, 3. Februar 14 Clojure • • • In Clojure gibt es Lazyness und Impurity Es ist dem Programmierer überlassen, ob er mischt Das kann zu Problemen führen user=> (def c (map (fn [x] (print ".")) [1 2 3])) #'user/c user=> c (...nil nil nil) user=> c (nil nil nil) Montag, 3. Februar 14 Probleme mit Lazyness • • • • (map create-handler gui-buttons) Kann in der REPL funktionieren, aber nicht im Programm In der REPL eingegeben füht der print zur Auswertung Wenn die Werte im Programm nicht benutzt werden, wird der Code evtl. niemals ausgeführt (doall (map create-handler gui-buttons)) Montag, 3. Februar 14 Monoide Montag, 3. Februar 14 Definition • Ein Monoid ist ein Tripel (S,r,e) • S ist eine Menge • r ist eine assoziative Verknüpfung von S×S nach S • e ist das neutrale Element von S bezüglich r Montag, 3. Februar 14 Beispiele • (ℕ, +, 0) ist ein Monoid • • Montag, 3. Februar 14 (ℕ,*,1) ist ein Monoid (ℕ,-,0) ist kein Monoid Monoid in Haskell class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty Montag, 3. Februar 14 Monoid in Haskell class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty neutrales Element Montag, 3. Februar 14 Monoid in Haskell class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty Verknüpfung Montag, 3. Februar 14 Monoid in Haskell class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty "reduce" Montag, 3. Februar 14 Monoid in Haskell class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty • Die Gesetze für Monoide • mempty ist das neutrale Element bezgl. mappend mempty `mappend` x = x x `mappend` mempty = x • mappend ist assoziativ (x `mappend` y) `mappend` z = x `mappend` (y `mappend` z) Montag, 3. Februar 14 Listen sind Monoide instance Monoid [a] where mempty = [] mappend = (++) Sind die Monoid Gesetze erfüllt? Montag, 3. Februar 14 Listen sind Monoide Zu zeigen: • neutrales Element: instance Monoid [a] where mempty = [] mappend = (++) mempty `mappend` x = x x `mappend` mempty = x mempty `mappend` [e1,e2,..] = [] ++ [e1,e2,..] = [e1,e2,..] [e1,e2,..] `mappend` mempty = [e1,e2,..] ++ [] = [e1,e2,..] • ✓ Assoziativität: ([a1,a2,..] `mappend` [b1,b2,..]) `mappend` [c1,c2,..] = ([a1,a2,..] ++ [b1,b2,..]) ++ [c1,c2,..] = [a1,a2,..] ++ ([b1,b2,..] ++[c1,c2,..]) = [a1,a2,..] `mappend` ([b1,b2,..]) `mappend` [c1,c2,..]) Montag, 3. Februar 14 ✓ Maybe ist ein Monoid (wenn der innere Typ ein Monoid ist) instance Monoid a => Monoid (Maybe a) where mempty = Nothing Nothing `mappend` m = m m `mappend` Nothing = m Just m1 `mappend` Just m2 = Just (m1 `mappend` m2) Sind die Monoid Gesetze erfüllt? Übung! Montag, 3. Februar 14 Funktoren, Applicatives, und Monaden Montag, 3. Februar 14 Wozu? • Monaden erlauben Komposition in Gegenwart eines Kontexts • Teile des Infrastrukturcodes können abstrahiert werden • Erleichtert Wiederverwertung Montag, 3. Februar 14 Funktoren • • • • • Functor ist eine Typklasse für Dinge, über die man mappen kann Intuitiv kann man an Listen oder Bäume denken, aber es ist eigentlich genereller fmap nimmt eine Funktion von a nach b und einen Wert vom Typ f a als Argumente und gibt einen Wert vom Typ f b zurück f ist ein Typ Konstruktor class Functor f where fmap :: (a -> b) -> f a -> f b Montag, 3. Februar 14 Funktoren • • • • • Functor ist eine Typklasse für Dinge, über die man mappen kann Intuitiv kann man an Listen oder Bäume denken, aber es ist eigentlich genereller fmap nimmt eine Funktion von a nach b und einen Wert vom Typ f a als Argumente und gibt einen Wert vom Typ f b zurück f ist ein Typ Konstruktor z.B. Maybe Int class Functor f where fmap :: (a -> b) -> f a -> f b Montag, 3. Februar 14 Gesetze • Funktoren müssen folgende Gesetze erfüllen • • fmap id = id fmap (f . g) = fmap f . fmap g • Montag, 3. Februar 14 anders geschrieben: fmap (f . g) x = fmap f (fmap g x) Listen als Funktor data [] a = [] | a : [a] instance Functor [] where fmap = map *Main> fmap (\x -> -x) [] [] *Main> fmap (\x -> -x) (1:(2:[])) [-1,-2] Sind die Gesetze erfüllt? 1) zu Zeigen: map id = id map id [e1,e2,...] = [(id e1), (id e2), ...] = [e1, e2, ...] = id([e1,e2,...]) 2) zu Zeigen: map (f.g) somelist = map f (map g somelist) map (f.g) [e1, e2,...] = [(f.g) e1, (f.g) e2,...] = [f(g(e1), f(g(e2)),...] map f (map g [e1, e2,...]) = map f [g(e1),g(e2),...] = [f(g(e1), f(g(e2)),...] ✓ ✔ Montag, 3. Februar 14 Funktoren in Bildern Ein einfacher Wert Eine Berechnung Quelle: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html Montag, 3. Februar 14 Funktoren in Bildern Ein "verpackter" Wert Prelude> :t Just 2 Just 2 :: Num a => Maybe a Prelude> :t (+ 3) (+ 3) :: Num a => a -> a Prelude> (+ 3) (Just 2) No instance for (Num (Maybe a0)) arising from a use of `+' Montag, 3. Februar 14 +3 weiss nicht, was es mit einem verpackten Wert anstellen soll Funktoren in Bildern Das für Maybe definierte fmap weiss, wie es die Funktion anwendet Montag, 3. Februar 14 Funktoren in Bildern Funktionsweise von fmap für Maybe Montag, 3. Februar 14 Maybe als Funktor data Maybe a = Just a | Nothing instance Functor Maybe where fmap :: (a -> b) -> f a -> f b fmap _ Nothing = Nothing fmap f Just a = Just (f a) *Main> fmap (\x -> -x) Nothing Nothing *Main> fmap (\x -> -x) (Just 4) Just (-4) Sind die Gesetze erfüllt? Montag, 3. Februar 14 Composition • Was passiert, wenn man mit fmap eine Funktion auf eine Funktion anwendet? > let f = fmap (+ 3) (+ 6) > f 10 19 Alias für fmap Montag, 3. Februar 14 Funktionen sind Funktoren instance Functor ((->) r) where fmap f g = f . g Montag, 3. Februar 14 Applikative Funktoren • • • • Wie passen Funktoren und Currying zusammen? Bsp.: fmap (fmap (+) (Just 1)) (Just 2) Das funktioniert nicht. fmap (+) (Just 1) liefert Just (+1) Der nächste Schritt funktioniert aber nicht Applicative definiert zwei Funktionen: pure und <*> pure "verpackt" einen normalen Wert <*> entspricht fmap • • class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b Montag, 3. Februar 14 Applikative Funktoren • • • • Wie passen Funktoren und Currying zusammen? Bsp.: fmap (fmap (+) (Just 1)) (Just 2) Das funktioniert nicht. fmap (+) (Just 1) liefert Just (+1) Der nächste Schritt funktioniert aber nicht Applicative definiert zwei Funktionen: pure und <*> pure "verpackt" einen normalen Wert <*> entspricht fmap • • class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b Jeder applikative Funktor ist auch ein Funktor Montag, 3. Februar 14 Maybe als Applicative class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something Montag, 3. Februar 14 Maybe als Applicative instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something > pure (+ 6) <*> Just 4 Just 10 > pure (+ 7) <*> (pure (+ 6) <*> Just 4) Just 17 > pure (+) <*> Just 4 <*> Just 2 -- applicative style Just 6 > pure (+) <*> Just 4 -- kann nicht geprinted werden, aber im Prinzip: Just (+ 4) Applicative Style löst unser Currying Problem Montag, 3. Februar 14 Applicatives Montag, 3. Februar 14 Listen > [(*2), (+3)] <*> [1, 2, 3] [2, 4, 6, 4, 5, 6] Montag, 3. Februar 14 Monaden Montag, 3. Februar 14 <$>,<*>, >>= class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (<$>) :: (Functor f) => (a -> b) -> f a -> f b (<*>) :: (Applicative f) => f (a -> b) -> f a -> f b (>>=) :: (Monad m) => m a -> (a -> m b) -> m b bind Montag, 3. Februar 14 the old switcheroo Gesetze • • • • Montag, 3. Februar 14 Wie für Funktoren und Applicatives gibt es auch für Monaden Gesetze Erinnern an Monoidgesetze. Kein Wunder, Monaden sind ja nur Monoide in der Kategorie der Endofunktoren! Links/Rechts Identität • • return a >>= f = f a m >>= return = m Assoziativität • (m >>= f) >>= g = m >== (\x -> f x >>= g) a → Maybe b half x = if even x then Just (x `div` 2) else Nothing Montag, 3. Februar 14 >>= class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b Montag, 3. Februar 14 Bind für Maybe Montag, 3. Februar 14 Composition Montag, 3. Februar 14 Cave Return! return ist nicht was ihr kennt !!! • Bind benötigt einen monadischen Wert • return verpackt einen Wert in der Monade > return 20 >>= half >>= half Just 5 > return 10 >>= half >>= half Nothing Montag, 3. Februar 14 do Notation • Angenommen wir wollen eine Zahl in einen String umwandeln • Und ein Ausrufezeichen anhängen • Im Maybe Kontext Montag, 3. Februar 14 do Notation foo = Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))) foo = do x <- Just 3 y <- Just "!" Just (show x ++ y) Montag, 3. Februar 14 WTF??? • • • • Warum nicht einfach show 3 ++ "!" ? Nicht vergessen: Wir sind im Maybe Kontext Unser Code ist vollkommen frei von Tests auf Nothing Die Essenz der fehlschlagenden Berechnung ist vollständig in bind implementiert > do x <- Just 2 ; y <- Just "!" ; Just (show x ++ y) Just "2!" > do x <- Nothing ; y <- Just "!" ; Just (show x ++ y) Nothing > do x <- Just 2 ; y <- Nothing ; Just (show x ++ y) Nothing Montag, 3. Februar 14 Monaden • Identity • List / Sequence • Maybe • State • ... Montag, 3. Februar 14 Identitätsmonade? • return ist die Identität • bind wendet nur die Funktion an • Wozu sollte sowas nutzen? > data Id a = Id > instance Monad > let foo x = Id > do x <- Id 2; Id 4 a deriving Show Id where return = Id; (Id a) >>= f = f a (x * x) y <- foo x; return y (let [x 2 y (* x x) y) Montag, 3. Februar 14 Monaden in Clojure • Mindestens zwei Frameworks • [uncomplicate/fluokitten • [org.clojure/algo.monads Montag, 3. Februar 14 "0.3.0"] "0.1.4"]