Pattern matching, Lazyness, Monaden

Werbung
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"]
Herunterladen