• Keine Ergebnisse gefunden

Fortgeschrittene Funktionale Programmierung

N/A
N/A
Protected

Academic year: 2022

Aktie "Fortgeschrittene Funktionale Programmierung"

Copied!
34
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Fortgeschrittene Funktionale Programmierung

9. Vorlesung

Janis Voigtl¨ander

Universit¨at Bonn

Wintersemester 2015/16

(2)

© 2014J. Voigtländer Deskriptive Programmierung 371

Ein zusätzliches Interface zur Parser-Bibliothek

Etwas „(gar nicht so) dunkle Magie“ (MParserCore.hs):

import qualified ParserCore

newtype Parser a = P (ParserCore.Parser a) unP :: Parser a ! ParserCore.Parser a unP (P p) = p

parse :: Parser a ! String ! a parse = ParserCore.parse . unP

item :: Parser Char item = P ParserCore.item

instance Monad Parser where return = yield

(>>=) = (++>) fail _ = failure

(3)

© Deskriptive Programmierung 372

Ein zusätzliches Interface zur Parser-Bibliothek

Nun Verwendung von do-Blöcken möglich (nicht erzwungen), zum Beispiel:

statt:

term :: Parser Expr term = do f à factor

char '*' t à term return (Mul f t) | | | factor

term :: Parser Expr

term = factor ++> (\f ! char '*' +++ term ++> \t ! yield (Mul f t)) | | | factor

factor :: Parser Expr factor = mapP Lit nat

| | | char '(' +++ expr ++> \e ! char ')' +++ yield e factor :: Parser Expr factor = mapP Lit nat | | | do char '(' e à expr char ')' return e

(4)

© 2014J. Voigtländer

Ein-/Ausgabe in Haskell, ganz einfaches Beispiel

Deskriptive Programmierung 314

• In „reinen“ Funktionen ist keine Interaktion mit Betriebssystem/Nutzer/… möglich.

• Es gibt jedoch eine spezielle do-Notation, die Interaktion ermöglicht, und aus der man „normale“ Funktionen aufrufen kann.

5 8 1680 Einfaches Beispiel:

prod :: [Int] ! Int prod [ ] = 1

prod (x:xs) = x * prod xs main = do n à readLn m à readLn print (prod [n..m])

reine Funktion

„Hauptschleife“

Programmablauf Eingabe

Eingabe Ausgabe

(5)

©

• Natürlich stehen auch im Kontext von IO-behafteten Berechnungen alle Features und Abstraktionsmittel von Haskell zur Verfügung, also wir definieren Funktionen mit Rekursion, verwenden Datentypen, Polymorphie, Higher-Order, …

• Ein „komplexeres“ Beispiel:

• Was „nein, nicht, auf keinen Fall“ geht, ist aus einem IO-Wert direkt (abseits der expliziten Sequenzialisierung und Bindung in einem do-Block) den gekapselten Wert zu entnehmen.

• Neben den gesehenen Primitiven für Ein-/Ausgabe per Terminal gibt es Primitiven und Bibliotheken für File-IO, Netzwerkkommunikation, GUIs, …

Prinzipielles zu Ein-/Ausgabe in Haskell: IO-Typen und do-Notation

Deskriptive Programmierung 317

dialog = do putStr "Eingabe: "

s à getLine if s == "end"

then return () else do let n = read s

putStrLn ("Ausgabe: " ++ show (n*n)) dialog

(6)

Aber fangen wir mit einem

” reinen“ Beispiel an

Zur Erinnerung:

dataExpr ::∗ → ∗where Lit :: Int→Expr Int

Add :: Expr Int→Expr Int→Expr Int Sub :: Expr Int→Expr Int→Expr Int Mul :: Expr Int→Expr Int→Expr Int Equal :: Eqt ⇒Exprt→Exprt →Expr Bool Not :: Expr Bool→Expr Bool

And :: Expr Bool→Expr Bool→Expr Bool If :: Expr Bool→Exprt →Exprt →Exprt eval:: Exprt →t

eval(Litn) =n

eval(Adde1 e2) =evale1+evale2

eval(Sube1e2) =evale1−evale2

eval(Mule1 e2) =evale1∗evale2 . . .

5

(7)

Sinnvolle Fehlerbehandlung

Angenommen, wir wollen Division hinzuf¨ugen:

dataExpr ::∗ → ∗where . . .

Div :: Expr Int→Expr Int→Expr Int . . .

Nicht so toll:

eval:: Exprt →t . . .

eval(Dive1 e2) =evale1 ‘div‘evale2 . . .

Wegen:

>eval(Div (Lit1) (Lit0))

*** Exception: divide by zero

(8)

Sinnvolle Fehlerbehandlung

Eine m¨ogliche L¨osung:

eval:: Exprt →Maybet eval(Litn) = Justn

eval(Adde1 e2) =caseevale1 of Nothing→Nothing

Justn1 →caseevale2 of Nothing→Nothing Justn2 →Just (n1+n2) . . .

eval(Dive1 e2) =caseevale1 of Nothing→Nothing

Justn1 →caseevale2 of Nothing→Nothing Justn2 →if n2 ==0

thenNothing

elseJust (n1 ‘div‘n2) . . .

7

(9)

Sinnvolle Fehlerbehandlung

Dann:

>eval(Div (Lit1) (Lit0)) Nothing

>eval(Add (Div (Lit1) (Mul (Lit0) (Lit3))) (Lit2)) Nothing

Dar¨uber hinaus jetzt m¨oglich:

dataExpr ::∗ → ∗where . . .

TryElse :: Exprt →Exprt →Exprt eval:: Exprt →Maybet

. . .

eval(TryElsee1e2) =caseevale1of Nothing→evale2

Justt →Justt

Und dann:

>eval(Div (Lit12) (TryElse (Add (Div (Lit1) (Mul (Lit0) (Lit3))) (Lit2))

(Lit3))) Just4

(10)

Sinnvolle Fehlerbehandlung

Dar¨uber hinaus jetzt m¨oglich:

dataExpr ::∗ → ∗where . . .

TryElse :: Exprt →Exprt →Exprt eval:: Exprt →Maybet

. . .

eval(TryElsee1e2) =caseevale1of Nothing→evale2

Justt →Justt Und dann:

>eval(Div (Lit12) (TryElse (Add (Div (Lit1) (Mul (Lit0) (Lit3))) (Lit2))

(Lit3))) Just4

8

(11)

” Plumbing“

Aber jetzt Codestruktur nicht so toll:

eval:: Exprt →Maybet eval(Litn) = Justn

eval(Adde1 e2) =caseevale1 of Nothing→Nothing

Justn1 →caseevale2 of Nothing→Nothing Justn2 →Just (n1+n2) eval(Sube1e2) =caseevale1 of

Nothing→Nothing

Justn1 →caseevale2 of Nothing→Nothing Justn2 →Just (n1−n2) . . .

(12)

” Plumbing“

Das kriegen wir aber auch in den Griff, bzw. k¨urzer hin:

andThen:: Maybea→(a→Maybeb)→Maybeb andThenNothingf = Nothing

andThen(Justn) f =f n eval:: Exprt →Maybet eval(Litn) = Justn

eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2 →Just (n1+n2) eval(Sube1e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2 →Just (n1−n2) . . .

eval:: Exprt →Maybet eval(Litn) = Justn

eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘ λn2 →Just (n1+n2) . . .

eval(Dive1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘ λn2 →ifn2==0

thenNothing

elseJust (n1 ‘div‘n2) . . .

eval(TryElsee1e2) =caseevale1of Nothing→evale2 Justt →Justt

10

(13)

” Plumbing“

Das kriegen wir aber auch in den Griff, bzw. k¨urzer hin:

eval:: Exprt →Maybet eval(Litn) = Justn

eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2 →Just (n1+n2) . . .

eval(Dive1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2 →ifn2==0 thenNothing

elseJust (n1 ‘div‘n2) . . .

eval(TryElsee1e2) =caseevale1of Nothing→evale2 Justt →Justt

(14)

Angenommen, wir wollen Variablen hinzuf¨ ugen:

dataExpr ::∗ → ∗where . . .

Var :: String→Expr Int . . .

”Plumbing“:

eval:: Exprt →(String→Int)→t eval(Litn) env =n

eval(Vars) env =env s

eval(Adde1 e2)env =evale1 env +evale2 env . . .

Dann:

>eval(Add (Lit3) (Var"x")) (λs →ifs =="x"then3else0) 6

>eval(Add (Lit3) (Var"y")) (λs →ifs =="x"then3else0) 3

11

(15)

Refactoring analog zu vorhin:

typeEnva= (String→Int)→a

andThen:: Env a→(a→Envb)→Envb andThenm f env =f (m env)env

eval:: Exprt →Envt eval(Litn) =constn eval(Vars) =λenv →env s eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2 →const(n1+n2) . . .

Nun kann man sich durchaus fragen, ob sich das Refactoring in diesemFalle wirklich gelohnt hat (neben der Tatsache, dass jetzt explizit ist, an welchen Stellenenv uberhaupt relevant ist). . .¨

(16)

Etwas bizarr, zum Zwecke der Illustration . . .

Aber stellen wir uns doch mal vor, wir m¨ochten, dass Variablen nach erstem Auslesen auf0gesetzt werden, also:

>eval(Add (Lit3) (Var"x")) (λs →ifs =="x"then3else0) 6

>eval(Add (Var"x") (Var"x")) (λs →if s =="x"then3else0) 3

Das auf Basis von:

eval:: Exprt →(String→Int)→t eval(Litn) env =n

eval(Vars) env =env s

eval(Adde1 e2)env =evale1 env +evale2 env . . .

machen?

Oh, das macht keinen Spaß!

13

(17)

Jedoch:

Von:

typeEnva= (String→Int)→a

andThen:: Env a→(a→Envb)→Envb andThenm f env =f (m env)env

eval:: Exprt →Envt eval(Litn) =constn eval(Vars) =λenv →env s eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2 →const(n1+n2) . . .

zu:

typeStrangeEnva= (String→Int)→(a,String→Int)

andThen:: StrangeEnva→(a→StrangeEnvb)→StrangeEnvb andThenm f env =let(a,env0) =m env

inf a env0 eval:: Exprt →StrangeEnvt eval(Litn) =λenv →(n,env)

eval(Vars) =λenv →(env s, λs0→if s ==s0 then0elseenv s0) eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2→λenv →(n1+n2,env) . . .

14

(18)

Jedoch:

Von:

. . . zu:

typeStrangeEnva= (String→Int)→(a,String→Int)

andThen:: StrangeEnva→(a→StrangeEnvb)→StrangeEnvb andThenm f env =let(a,env0) =m env

inf a env0 eval:: Exprt →StrangeEnvt eval(Litn) =λenv →(n,env)

eval(Vars) =λenv →(env s, λs0→if s ==s0 then0elseenv s0) eval(Adde1 e2) =evale1 ‘andThen‘

λn1 →evale2 ‘andThen‘

λn2→λenv →(n1+n2,env) . . .

ist nicht arg so schmerzhaft.

14

(19)

Abstraktion!

Wir hatten jetzt:

andThen:: Maybea→(a→Maybeb)→Maybeb andThenNothingf = Nothing

andThen(Justn) f =f n und:

typeEnva= (String→Int)→a

andThen:: Env a→(a→Envb)→Envb andThenm f env =f (m env)env

und:

typeStrangeEnva= (String→Int)→(a,String→Int)

andThen:: StrangeEnva→(a→StrangeEnvb)→StrangeEnvb andThenm f env =let(a,env0) =m env

inf a env0

(20)

Abstraktion!

Und wir hatten:

eval:: ExprtMaybet eval(Litn) = Justn

eval(Adde1e2) =evale1‘andThen‘

λn1evale2‘andThen‘

λn2Just (n1+n2)

bzw.

eval:: ExprtEnvt eval(Litn) =constn eval(Vars) =λenvenv s eval(Adde1e2) =evale1‘andThen‘

λn1evale2‘andThen‘

λn2const(n1+n2)

bzw.

eval:: ExprtStrangeEnvt eval(Litn) =λenv(n,env)

eval(Vars) =λenv(env s, λs0ifs==s0then0elseenv s0) eval(Adde1e2) =evale1‘andThen‘

λn1evale2‘andThen‘

λn2λenv(n1+n2,env) 16

(21)

Abstraktion!

Das schreit geradezu nach:

return::a→Maybea returna= Justa und:

typeEnva= (String→Int)→a return::a→Enva

returna=consta und:

typeStrangeEnva= (String→Int)→(a,String→Int) return::a→StrangeEnva

returna=λenv →(a,env)

(22)

Abstraktion!

Dann n¨amlich:

eval:: ExprtMaybet eval(Litn) =returnn

eval(Adde1e2) =evale1‘andThen‘

λn1evale2‘andThen‘

λn2return(n1+n2)

bzw.:

eval:: ExprtEnvt eval(Litn) =returnn eval(Vars) =λenvenv s eval(Adde1e2) =evale1‘andThen‘

λn1evale2‘andThen‘

λn2return(n1+n2)

bzw.:

eval:: ExprtStrangeEnvt eval(Litn) =returnn

eval(Vars) =λenv(env s, λs0ifs==s0then0elseenv s0) eval(Adde1e2) =evale1‘andThen‘

λn1evale2‘andThen‘

λn2return(n1+n2) 18

(23)

Was ist denn nun eine Monade?

Was immer wir uns w¨unschen, solange es (mindestens) folgendes Interface hat:

andThen:: Ma→(a→Mb)→Mb return ::a→Ma

f¨ur irgendein konkretes M.

Bzw. in Haskell eingebettet als Typkonstruktorklasse:

classMonadmwhere

(>>=) ::m a→(a→m b)→m b return::a→m a

Genaugenommen auch noch:

fail :: String→m a

(24)

Was ist denn nun eine Monade?

Was immer wir uns w¨unschen, solange es (mindestens) folgendes Interface hat:

andThen:: Ma→(a→Mb)→Mb return ::a→Ma

f¨ur irgendein konkretes M.

Wir haben gesehen:

andThen:: Maybea→(a→Maybeb)→Maybeb return ::a→Maybea

typeEnva= (String→Int)→a

andThen:: Env a→(a→Envb)→Envb return ::a→Enva

typeStrangeEnva= (String→Int)→(a,String→Int)

andThen:: StrangeEnva→(a→StrangeEnvb)→StrangeEnvb return ::a→StrangeEnva

19

(25)

Was ist denn nun eine Monade?

Was immer wir uns w¨unschen, solange es (mindestens) folgendes Interface hat:

andThen:: Ma→(a→Mb)→Mb return ::a→Ma

f¨ur irgendein konkretes M.

Außerdem fordert man noch die G¨ultigkeit folgender Gesetze:

(returna) ‘andThen‘k = k a m ‘andThen‘return = m

(m ‘andThen‘k) ‘andThen‘q = m‘andThen‘

(λa→(k a) ‘andThen‘q) (Nur leider lassen sie sich in Haskell nicht erzwingen.)

(26)

Instanzenbildung

Wenn wir mal von der Tatsache absehen, dass man von Typsynonymen

”eigentlich“ nicht einfach Typklasseninstanzen bilden darf, kriegen wir:

instanceMonad Maybewhere fail = Nothing

returna= Justa Nothing>>=f = Nothing Justn >>=f =f n instanceMonad Envwhere

returna=consta

m>>=f =λenv →f (m env)env instanceMonad StrangeEnvwhere

returna=λenv →(a,env)

m>>=f =λenv →let(a,env0) =m env inf a env0

20

(27)

. . . und entsprechender Einsatz:

eval:: ExprtMaybet eval(Litn) =returnn eval(Adde1e2) =evale1>>=

λn1evale2>>=

λn2return(n1+n2) . . .

eval:: ExprtEnvt eval(Litn) =returnn eval(Vars) =λenvenv s eval(Adde1e2) =evale1>>=

λn1evale2>>=

λn2return(n1+n2) . . .

eval:: ExprtStrangeEnvt eval(Litn) =returnn

eval(Vars) =λenv(env s, λs0ifs==s0then0elseenv s0) eval(Adde1e2) =evale1>>=

λn1evale2>>=

λn2return(n1+n2) 21

(28)

Lohnt der ganze Aufwand?

Was haben wir denn nun eigentlichgewonnen(neben der rein syntaktischen ¨Ahnlichkeit verschiedener Auswertungsfunktionen)?

Nun, h¨atten wir unseren Ursprungsauswerter gleich so geschrieben:

eval:: Exprt →t

eval(Litn) =returnn eval(Adde1 e2) =evale1>>=

λn1 →evale2>>=

λn2 →return(n1+n2) . . .

eval(Note) =evale>>=λn→return(notn) . . .

bzw. sogar mit dem allgemeineren Typ eval:: Monadm⇒Exprt →m t (aber gleicher Definition), dann . . .

22

(29)

Lohnt der ganze Aufwand?

. . . h¨atten wir unsere Spracherweiterungen sehr fokussiert umsetzen k¨onnen:

f¨ur Exceptions: lediglich hinzuf¨ugen (und ausm wird Maybe):

eval(Dive1e2) =evale1>>=

λn1evale2>>= λn2ifn2==0

thenfail"..."

elsereturn(n1 divn2) eval(TryElsee1e2) =caseevale1of

Nothingevale2 okay okay

f¨ur Variablen: lediglich hinzuf¨ugen (und ausm wird Env):

eval(Vars) =λenvenv s

f¨ur

”bizarre“

Variablen: lediglich hinzuf¨ugen (und ausm wird StrangeEnv):

eval(Vars) =λenv(env s, λs0ifs==s0then0elseenv s0)

(30)

Allerdings. . .

. . . mindestens zwei große

”Aber“:

1. Von dem urspr¨unglichen Auswerter:

eval:: Exprt→t eval(Litn) =n

eval(Adde1 e2) =evale1+evale2

eval(Sube1 e2) =evale1−evale2

eval(Mule1 e2) =evale1∗evale2 . . .

umzuschwenken auf:

eval:: Monadm⇒Exprt →m t eval(Litn) =returnn eval(Adde1 e2) =evale1>>=

λn1→evale2>>=

λn2→return(n1+n2) . . .

erfordert nat¨urlich einen gewissen Aufwand. 24

(31)

Allerdings. . .

. . . mindestens zwei große

”Aber“:

2. Wer sagt uns eigentlich, dass das Ganze auch jenseits unserer drei Anwendungsf¨alle

”Exceptions“,

”Variablen“ und

”bizarre Variablen“ tr¨agt?

Und dass das Power-to-Weight-Ratio stimmt?

(32)

Zun¨ achst zu 1.:

Spezielle Compilerunterst¨utzung f¨ur

”do-Notation“:

Aus:

eval:: MonadmExprtm t eval(Litn) =returnn eval(Adde1e2) =evale1>>=

λn1evale2>>=

λn2return(n1+n2) . . .

wird:

eval:: MonadmExprtm t eval(Litn) =returnn eval(Adde1e2) =don1evale1

n2evale2 return(n1+n2) . . .

Außerdem im Prinzip automatische Unterst¨utzung (zum ¨Ubergang von etwaeval(Adde1e2) =evale1+evale2) m¨oglich. 25

(33)

Zun¨ achst zu 1.:

Realisierung der

”do-Notation“: einfache Syntaxtransformation

doe e

dop ←e letokp =dostmts stmts ok =fail"..."

ine>>=ok do letdecls letdecls

stmts in dostmts

doe e>>=λ →dostmts stmts

I wirklich nur syntaktischer Zucker (wie list comprehensions)

I returnhat nichts mit C (oder so) zu tun: ganz normale Funktion;

”springt“ nicht; ist nicht immer am Blockende; . . .

I nicht jede monadische Berechnung innerhalb do-Block

I manchmal n¨otig,do-Bl¨ocke zu schachteln (da exakt, und nur, obige Regeln verwendet)

(34)

Nun zu 2.:

2. Wer sagt uns eigentlich, dass das Ganze auch jenseits unserer drei Anwendungsf¨alle

”Exceptions“,

”Variablen“ und

”bizarre Variablen“ tr¨agt?

Und dass das Power-to-Weight-Ratio stimmt?

I Beweis durch Autorit¨at: . . . sonst h¨atte man sich wohl nicht die M¨uhe der Sonderbehandlung im Compiler gemacht.

I Beweis durch Masse: → Hoogle, insbesondere die Vielzahl an Funktionen, die polymorph ¨uber Monaden sind

I Beweis durch Ausprobieren: . . .

28

Referenzen

ÄHNLICHE DOKUMENTE

Grund 6: (nach B. MacLennan, Functional Programming) Funktionale Programmierung ist eng verknüpft mit

I Problem: plaziere 8 Damen sicher auf einem Schachbrett.

Im Laufe des Semesters gilt: An der H¨ alfte der w¨ ochentlichen Ubungstermine werden zur Pr¨ ¨ ufungszulassung herangezogene theoretische oder praktische Aufgaben gestellt.. L¨

I Außerdem kann eine Operation noch geteilte Kosten s i haben (potentielle zuk¨ unftige Kosten selbst angelegter, nicht direkt ausgewerteter Thunks), diese werden als Debits in

Nun erstellt man eine Tabelle, in der man für jede Taste i und jeden Buchstaben j speichert, wie die optimalen Kosten wären, wenn es weder frühere Tasten noch frühere Buchstaben

Wir wollen eine formale Sprachbeschreibung (mit Details zu Syntax, Typsystem, Semantik) f¨ ur Haskell, bzw. f¨ ur eine ausgew¨ ahlte Teilsprache davon.. Einige Beschr¨ ankungen, die

I Aber verlange, dass ¨ aquivalente Terme in jedem m¨ oglichen Kontext zu gleichen Beobachtungen f¨ uhren. I Also, w¨ ahle als ≡ die gr¨ oßte Kongruenzrelation, die bez¨ uglich

kein Zusammenhang zwischen Zero und Succ auch der unsinnige Typ Vec Bool String ist erlaubt Ursache: Kind von Vec ist zu allgemein: * -> * -> *. Wunsch-Kind: Nat -> * ->