• Keine Ergebnisse gefunden

Funktionale Programmierung - fortgeschrittene Konzepte und Anwendungen Vorlesung 11 vom 17.12.2019 + 07.01.2020: Monaden als Berechnungsmuster

N/A
N/A
Protected

Academic year: 2022

Aktie "Funktionale Programmierung - fortgeschrittene Konzepte und Anwendungen Vorlesung 11 vom 17.12.2019 + 07.01.2020: Monaden als Berechnungsmuster"

Copied!
45
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Funktionale Programmierung - fortgeschrittene Konzepte und Anwendungen

Vorlesung 11 vom 17.12.2019 + 07.01.2020: Monaden als Berechnungsmuster

Till Mossakowski

Otto-von-Guericke Universität Magdeburg

Wintersemester 2019/20

(2)

Fahrplan

I

Teil I: Funktionale Programmierung im Kleinen

I

Teil II: Funktionale Programmierung im Großen

I

Teil III: Funktionale Programmierung im richtigen Leben

I Aktionen und Zustände

I Monaden als Berechnungsmuster I Parser

I Webentwicklung I Linsen

(3)

Inhalt

I

Wie geht das mit IO?

I

Das M-Wort

I

Monaden als allgemeine Berechnungsmuster

I

Fallbeispiel: Auswertung von Ausdrücken

(4)

Zustandsabhängige

Berechnungen

(5)

Funktionen mit Zustand

I

Idee: Seiteneffekt explizit machen

I

Funktion

f

:

AB

mit Seiteneffekt in Zustand

S:

f

:

A×SB×S

=

f

:

ASB×S

I

Datentyp:

SB×S

I

Komposition: Funktionskomposition und uncurry

curry :: ((α, β )

γ )→ α→ β→ γ

uncurry :: (α→ β→ γ)

(α , β)

γ

(6)

In Haskell: Zustände explizit

I

Zustandstransformer: Berechnung mit Seiteneffekt in Typ

σ

(polymorph über

α)

type State σ α = σ→ (α , σ)

I

Komposition zweier solcher Berechnungen:

comp :: State σ α→ (α→ State σ β)→ State σ β comp f g = uncurry g

f

I

Trivialer Zustandstransformer:

l i f t :: α→ State σ α l i f t = curry id

I

Lifting von Funktionen:

map :: (α→ β)→ State σ α

State σ β

map f g = (λ(a , s )→ ( f a , s ))

g

(7)

Zugriff auf den Zustand

I

Zustand lesen:

get :: (σ→ α)→ State σ α get f s = ( f s , s )

I

Zustand setzen:

set :: (σ→ σ)→ State σ ()

set g s = ( ( ) , g s )

(8)

Einfaches Beispiel

I

Zähler als Zustand:

type WithCounter α = State Int α

I

Beispiel: Funktion, die in Kleinbuchstaben konvertiert und zählt cntToL :: String→ WithCounter String

cntToL [ ] = l i f t ""

cntToL (x : xs )

|

isUpper x = cntToL xs ‘comp‘

λys→ set (+1) ‘comp‘

λ()→ l i f t (toLower x : ys )

|

otherwise = cntToL xs ‘comp‘ λys→ l i f t (x : ys )

I

Hauptfunktion (verkapselt State):

cntToLower :: String→ ( String , Int )

cntToLower s = cntToL s 0

(9)

Monaden

(10)

Monaden als Berechnungsmuster

I

In cntToL werden zustandsabhängige Berechnungen verkettet.

I

So ähnlich wie bei Aktionen!

State:

type State σ α

comp :: State σ α → (α→ State σ β)→

State σ β l i f t :: α→ State σ α

map :: (α→ β)→ State σ α→

State σ β

Aktionen:

type IO α

(=) :: IO α → (α→ IO β) → IO β

return :: α→ IO α

fmap :: (α→ β)→ IO α→ IO β

Berechnungsmuster:

Monade

(11)

Monaden als Berechngsmuster

Eine Monade ist:

I

mathematisch: durch Operationen und Gleichungen definiert (verallgemeinerte algebraische Theorie)

I

als Berechnungsmuster: verknüpfbare Berechnungen mit einem Ergebnis

I

in Haskell: durch mehrere Typklassen definierte Operationen mit

Eigenschaften

(12)

Monaden in Haskell

I

Aktion auf Funktionen:

class Functor f where

fmap :: (a

b)

f a

f b fmap bewahrt Identität und Komposition:

fmap id == id

fmap ( f

g) == fmap f

fmap g

I

Die Eigenschaften sollten gelten, können aber nicht überprüft werden.

I Standard: “Instances of Functor should satisfy the following laws.”

(13)

Monaden in Haskell

I

Verkettung ( = ) und Lifting (return):

class Functor m

Monad m where ( =) :: m a

(a

m b)

m b return :: a

m a

=ist assoziativ und return das neutrale Element:

return a = k == k a m = return == m

m = (x

k x = h) == (m = k) = h

I

Auch diese Eigenschaften können nicht geprüft werden.

I

Den syntaktischen Zucker (do-Notation) gibt’s umsonst dazu.

(14)

Beispiele für Monaden

I

Zustandstransformer: ST, State, Reader, Writer

I

Fehler und Ausnahmen: Maybe, Either

I

Mehrdeutige Berechnungen: L i s t, Set

(15)

Die Zustands-Monade

I

Der Zustands-Datentyp

data State σ α = St { run :: σ→ (α, σ) }

I

Instanzen:

instance Functor ( State σ) where

fmap f g = St $ λs→ (λ(a , s1 )→ ( f a , s1 )) (run g s ) instance Monad ( State σ) where

f = g = St $ λs

let (a , s ’ )= run f s in run (g a) s ’ return a = St $ λs→ (a , s )

I

Elementare Operationen:

get :: (σ→ α)→ State σ α

get f = St $ λs→ ( f s , s )

set :: (σ→ σ)→ State σ ()

set g = St $ λs→ ( ( ) , g s )

(16)

Einfaches Beispiel in do-Notation

I

Zähler als Zustand:

type WithCounter α = State Int α

I

Beispiel: Funktion, die in Kleinbuchstaben konvertiert und zählt cntToL :: String→ WithCounter String

cntToL [ ] = return ""

cntToL (x : xs )

|

isUpper x = do ys← cntToL xs set (+1)

return (toLower x : ys )

|

otherwise = do { ys← cntToL xs ; return (x : ys ) }

I

Hauptfunktion (verkapselt State):

cntToLower :: String→ ( String , Int )

cntToLower s = run (cntToL s ) 0

(17)

Die Reader-Monade

I

Aus dem Zustand wird nur gelesen:

data Reader σ α = R {run :: σ→ α}

I

Instanzen:

instance Functor (Reader σ) where fmap f (R g) = R ( f . g)

instance Monad (Reader σ) where return a = R ( const a)

R f = g = R $ λs→ run (g ( f s )) s

I

Nur eine elementare Operation:

get :: (σ→ α)→ Reader σ α

get f = R $ λs→ f s

(18)

Fehler und Ausnahmen

I

Maybe als Monade:

instance Functor Maybe where fmap f ( Just a) = Just ( f a) fmap f Nothing = Nothing instance Monad Maybe where

Just a = g = g a Nothing = g = Nothing return = Just

I Ähnlich mit Either

I

Berechnungsmodell: Ausnahmen (Fehler)

I f :: α→ Maybe βist Berechnung mit möglichem Fehler I Fehlerfreie Berechnungen werden verkettet

I Fehler (Nothingoder Left x) werden propagiert

(19)

Mehrdeutigkeit

I

L i s t als Monade:

I Können wir so nicht hinschreiben, Syntax vordefiniert

instance Functor [α] where

fmap = map

instance Monad [α] where

a : as = g = g a + + ( as = g) [ ] = g = [ ]

return a = [ a ]

I

Berechnungsmodell: Mehrdeutigkeit

I f :: α→ [β] ist Berechnung mitmehrerenmöglichen Ergebnissen I Verkettung: Anwendung der folgenden Funktion aufjedes Ergebnis

(concatMap)

(20)

Beispiel

I

Berechnung aller Permutationen einer Liste:

1 Ein Element überall in eine Liste einfügen:

i n s :: α→ [α]→ [ [α] ] i n s x [ ] = return [ x ]

i n s x (y : ys ) = return (x : y : ys ) ++ do i s ← i n s x ys

return $ y : i s

2 Damit Permutationen (rekursiv):

perms :: [α]→ [ [α] ] perms [ ] = return [ ] perms (x : xs ) =do

ps ← perms xs i s ← i n s x ps return i s

(21)

Die Listenmonade in der Listenkomprehension

I

Berechnung aller Permutationen einer Liste:

1 Ein Element überall in eine Liste einfügen:

ins ’ :: α→ [α]→ [ [α] ] ins ’ x [ ] = [ [ x ] ]

ins ’ x (y : ys ) = [ x : y : ys ] ++ [ y : i s | i s ← ( ins ’ x ys ) ]

2 Damit Permutationen (rekursiv):

perms ’ :: [α]→ [ [α] ] perms ’ [ ] = [ [ ] ]

perms ’ (x : xs ) = [ i s | ps ← perms ’ xs , i s ← ins ’ x ps ]

I

Listenkomprehension

= Listenmonade

(22)

IO ist keine Magie

(23)

Implizite vs. explizite Zustände

I

Wie funktioniert jetzt IO?

I

Nachteil von State: Zustand ist explizit

I Kanndupliziertwerden

I

Daher: Zustand implizit machen

I Datentypverkapseln(keinrun)

I Zugriff auf Statenur über elementare Operationen

(24)

Aktionen als Zustandstransformationen

I

Idee: Aktionen sind

Transformationen

auf Systemzustand

S I S

beinhaltet

I Speicher als AbbildungA*V (AdressenA, WerteV) I Zustand des Dateisystems

I Zustand des Zufallsgenerators I

In Haskell: Typ RealWorld

I “Virtueller” Typ, Zugriff nur über elementare Operationen I Entscheidend nurReihenfolgeder Aktionen

(25)

Fallbeispiel: Auswertung von

Ausdrücken

(26)

Monaden im Einsatz

I

Auswertung von Ausdrücken:

data Expr = Var String

|

Num Double

|

Plus Expr Expr

|

Minus Expr Expr

|

Times Expr Expr

|

Div Expr Expr

I

Mögliche Arten von Effekten:

I Partialität (Division durch 0) I Zustände (für die Variablen) I Mehrdeutigkeit

I

Auswertung ohne Effekte:

eval :: Expr → Double eval (Var _) = 0 eval (Num n) = n

eval ( Plus a b) = eval a+ eval b eval (Minus a b) = eval a− eval b eval (Times a b) = eval a∗ eval b eval (Div a b) = eval a/ eval b

(27)

Monaden im Einsatz

I

Auswertung von Ausdrücken:

data Expr = Var String

|

Num Double

|

Plus Expr Expr

|

Minus Expr Expr

|

Times Expr Expr

|

Div Expr Expr

I

Mögliche Arten von Effekten:

I Partialität (Division durch 0) I Zustände (für die Variablen) I Mehrdeutigkeit

I

Auswertung ohne Effekte:

eval :: Expr → Double eval (Var _) = 0 eval (Num n) = n

eval ( Plus a b) = eval a+ eval b eval (Minus a b) = eval a− eval b eval (Times a b) = eval a∗ eval b eval (Div a b) = eval a/ eval b

(28)

Auswertung mit Fehlern

I

Partialität durch Maybe-Monade

eval :: Expr → Maybe Double eval (Var _) = return 0 eval (Num n) = return n

eval ( Plus a b) =do x← eval a ; y← eval b ; return $ x+ y eval (Minus a b) = do x← eval a ; y← eval b ; return $ x−y eval (Times a b) = do x← eval a ; y← eval b ; return $ x∗ y eval (Div a b) =do

x← eval a ; y← eval b ; i f y == 0 then Nothing else Just $ x/ y

(29)

Auswertung mit Zustand

I

Zustand durch Reader-Monade

import ReaderMonad

import qualified Data .Map as M type State = M.Map String Double eval :: Expr → Reader State Double eval (Var i ) = get (M. ! i )

eval (Num n) = return n

eval ( Plus a b) =do x← eval a ; y← eval b ; return $ x+ y eval (Minus a b) = do x← eval a ; y← eval b ; return $ x−y eval (Times a b) = do x← eval a ; y← eval b ; return $ x∗ y eval (Div a b) =do x← eval a ; y← eval b ; return $ x/ y

(30)

Mehrdeutige Auswertung

I

Dazu: Erweiterung von Expr:

data Expr = Var String

|. . .

| Pick Expr Expr

eval :: Expr → [ Double ] eval (Var i ) = return 0 eval (Num n) = return n

eval ( Plus a b) =do x← eval a ; y← eval b ; return $ x+ y eval (Minus a b) = do x← eval a ; y← eval b ; return $ x−y eval (Times a b) = do x← eval a ; y← eval b ; return $ x∗ y eval (Div a b) =do x← eval a ; y← eval b ; return $ x/ y eval ( Pick a b) =do x← eval a ; y← eval b ; [ x , y ]

(31)

Kombination der Effekte

I

Benötigt Kombination der Monaden.

I

Monade Res:

I Zustandsabhängig I Mehrdeutig I Fehlerbehaftet

data Res σ α = Res { run :: σ→ [Maybe α ] }

I

Andere Kombinationen möglich:

data Res σ α = Res (σ→ Maybe [α] )

data Res σ α = Res (σ→ [α] )

data Res σ α = Res ( [σ→ α] )

(32)

Kombination der Effekte

I

Benötigt Kombination der Monaden.

I

Monade Res:

I Zustandsabhängig I Mehrdeutig I Fehlerbehaftet

data Res σ α = Res { run :: σ→ [Maybe α ] }

I

Andere Kombinationen möglich:

data Res σ α = Res (σ→ Maybe [α] ) data Res σ α = Res (σ→ [α] )

data Res σ α = Res ( [σ→ α] )

(33)

Kombination der Effekte

I

Benötigt Kombination der Monaden.

I

Monade Res:

I Zustandsabhängig I Mehrdeutig I Fehlerbehaftet

data Res σ α = Res { run :: σ→ [Maybe α ] }

I

Andere Kombinationen möglich:

data Res σ α = Res (σ→ Maybe [α] )

data Res σ α = Res (σ→ [α] )

data Res σ α = Res ( [σ→ α] )

(34)

Res: Monadeninstanz

I

Functor durch Komposition der fmap:

instance Functor (Res σ) where

fmap f (Res g) = Res $ fmap (fmap f ) . g

I

Monade ist Kombination

instance Monad (Res σ) where

return a = Res ( const [ Just a ] ) Res f= g = Res $ λs→ do ma← f s

case ma of

Just a→ run (g a) s Nothing→ return Nothing f a i l _ = Res $ const [ Nothing ]

(35)

Res: Operationen

I

Zugriff auf den Zustand:

get :: (σ→ α)→ Res σ α get f = Res $ λs→ [ Just $ f s ]

I

Mehrdeutige Ergebnisse:

j o i n :: α→ α→ Res σ α

j o i n a b = Res $ λs→ [ Just a , Just b ]

(36)

Auswertung mit Allem

I

In der Monaden Res können alle Effekte benutzt werden:

type State = M.Map String Double eval :: Expr → Res State Double eval (Var i ) = get (M. ! i ) eval (Num n) = return n

eval ( Plus a b) =do x← eval a ; y← eval b ; return $ x+ y eval (Minus a b) =do x← eval a ; y← eval b ; return $ x−y eval (Times a b) =do x← eval a ; y← eval b ; return $ x∗ y eval (Div a b) =do x← eval a ; y← eval b

i f y == 0 then f a i l "/0" else return $ x / y eval ( Pick a b) =do x← eval a ; y← eval b ; j o i n x y

(37)

Monadentransformer

I

Monadentransformer = Monade mit Platzhalter für weitere Monaden

I

ermöglicht systematische Kombination von Monaden

I

z.B. für Maybe:

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } instance (Monad m) ⇒ Monad (MaybeT m) where

return = MaybeT◦return◦Just x= f = MaybeT $ do

v ← runMaybeT x case v of

Nothing → return Nothing Just y → runMaybeT ( f y) f a i l _ = MaybeT ( return Nothing)

(38)

MaybeT ist ein Monadentransformer

class MonadTrans t where

l i f t :: (Monad m) ⇒m a → t m a instance MonadTrans MaybeT where

l i f t = MaybeT◦fmap Just

instance MonadTrans (ReaderT r ) where l i f t = liftReaderT

liftReaderT :: m a → ReaderT r m a liftReaderT m = ReaderT ( const m)

Sowohl Listen als auch Maybes können geliftet werden:

liftMaybe :: (Monad m) ⇒ Maybe a → MaybeT m a liftMaybe = MaybeT◦ return

type ListOfMaybe = MaybeT [ ] my_list :: ListOfMaybe Int my_list =do a ← l i f t [ 1 , 2 ]

b ← liftMaybe $ Just (a+1)

(39)

Monadentransformer ReaderT

newtype ReaderT r m a = ReaderT { runReaderT :: r →m a } instance (Monad m) ⇒ Monad (ReaderT r m) where

return = l i f t ◦return m= k = ReaderT $ λ r → do

a ← runReaderT m r runReaderT (k a) r f a i l msg = l i f t ( f a i l msg)

reader :: (Monad m) ⇒ ( r → a) → ReaderT r m a−−like get reader f = ReaderT ( return◦ f )

instance MonadTrans (ReaderT r ) where l i f t = liftReaderT

liftReaderT :: m a → ReaderT r m a liftReaderT m = ReaderT ( const m)

(40)

(Weitere) Beispiele für Monadentransformer

Außen wirkende Transformer:

ReaderT

für ein read-only-Environment (z.B. Konfiguration)

StateT

für einen globalen Zustand

Innen wirkende Transformer:

WriterT

für ein write-only-Environment (z.B. für Logging)

EitherT

für fehlschlagbare Operationen (mit Fehlermeldung)

MaybeT

für fehlschlagbare Operationen (ohne Fehlermeldung)

ListT

für Listen / Nichtdeterminismus

Je nachdem, welche Möglichkeiten man haben möchte, kann man diese

miteinander kombinieren.

(41)

Auswertung von Ausdrücken mittels Monadentransformern

Res = Reader von List von Maybe:

type Res σ= ReaderT σ (MaybeT [ ] ) get :: (σ → α) → Res σ α

get = reader

j o i n :: αα → Res σ α j o i n a b = l i f t $ l i f t [ a , b ] run :: Res σ ασ → [Maybeα]

run m s = runMaybeT $ runReaderT m s

type State = M.Map String Double eval :: Expr

Res State Double eval (Var i ) = get (M. ! i )

. . .

(exakt so wie vorher definiert)

(42)

Die Tücken der Kombination

Es kommt auf die Reihenfolge an:

StateT MyState ( EitherT String I d e n t i t y )

kann fehlschlagen, aber man kommt nach dem Fehlschlag noch an den State dran, wohingegen

EitherT String (StateT MyState I d e n t i t y )

nur die Fehlermeldung liefert und den State schon entsorgt hat.

Häufig findet man einen Read-Write-State-Transformer, kurz

RWST.

Echtweltprogramme sind oft durch einen

RWST IO

mit der Außenwelt verbunden.

(Diese und folgende Folien basieren auf Folien cby J. Betzendahl, S. Dresselhaus CC-BY-NC-SA)

(43)

Beispiel

Ein Echtwelt-Beispiel könnte etwa der folgende Aufruf sein:

ask :: (Monad m)

ReaderT r m r ask = ReaderT return

data Env = Env { filename :: String } readInputs :: ReaderT Env IO String readInputs = do

e

ask

f

l i f t I O $ readFile ( filename e) return f

Dieser Aufruf liest einen Dateinamen aus einem Environment, kann per

liftIO IO-Aktionen ausführen und das Ergebnis (den String mit dem

Dateiinhalt) zurückliefern.

(44)

Beispiel

Noch ein Beispiel aus einem Spiel könnte sein:

mainLoop :: RWST Env () State IO () mainLoop = do

e

ask

f

l i f t I O $ getUserInput ( keySettings e) oldWorld

get

let newWorld = updateWorld f oldWorld put newWorld

unless ( f == endKey e) mainLoop

Dies ist eine klassische Game-Loop, bestehend aus Konfigurationen im

Env

(Key settings),

IO

(User-Input abfragen), Update des internen Zustands (updateWorld) und das schreiben des neuen Zustandes (put

newWorld).

Wichtig:

updateWorld

ist pure!

(45)

Zusammenfassung

I

Monaden sind Muster für Berechnungen mit Seiteneffekten

I

Beispiele:

I Zustandstransformer (State)

I Fehler und Ausnahmen (Maybe, Either) I Nichtdeterminismus (L i s t)

I

Fallbeispiel Auswertung von Ausdrücken:

I Kombination aus Zustand, Partialität, Mehrdeutigkeit

I

Monadentransformer vereinfachen die Kombination von Monaden

Referenzen

ÄHNLICHE DOKUMENTE

I Funktionen sind gleichberechtigt: Ausdrücke wie alle anderen I Grundprinzip der funktionalen Programmierung. I Modellierung allgemeiner Berechungsmuster

Parser sind also Funktionen, die einen String einlesen und eine Liste von Paaren von was auch immer geparst werden soll und Strings zurück geben.. Die erste Konvention ist, dass

UserIdent :: EntityField User Text UserName :: EntityField User Text UserPassword :: EntityField User (Maybe Text) UserLastlogin :: EntityField User UTCTime.. Um den Rest kümmert

Das bedeutet wir können folgende Funktionen schreiben: lensR2Lens :: LensR s a -> Lens' s a.. lens2LensR :: Lens' s a -> LensR

Praktische Informatik 3: Funktionale Programmierung Vorlesung 11 vom 08.01.2019: Monaden als Berechnungsmuster..

I In Haskell: durch mehrere Typklassen definierte Operationen mit bestimmten Eigenschaften. I In Scala: ein Typ mit

Praktische Informatik 3: Funktionale Programmierung Vorlesung 11 vom 10.01.2017: Monaden als Berechnungsmuster..

I nach Graham Hutton, Erik Meijer: Monadic parsing in