Fortgeschrittene Funktionale Programmierung
6. und 7. Vorlesung
Janis Voigtl¨ander
Universit¨at Bonn
Wintersemester 2015/16
Zu einfache(n) Datentypen
Ein einfacher Datentyp:
dataTreea= Leaf a|Branch (Treea) (Treea) Und zwei Funktionen darauf:
mirror:: Treea→Treea
mirror(Brancht1 t2) = Branch (mirrort2) (mirrort1)
mirrort =t
subst:: Treea→Treeb →Treeb substt t0=got
wherego(Brancht1t2) = Branch (got1) (got2)
go =t0
Angenommen, wir w¨urden gern die Invariante ausdr¨ucken, dass es sich um vollst¨andige Bin¨arb¨aume handelt (und dass obige
Funktionen diese Eigenschaft erhalten).
1
Nested Datatypes
Rekursive Positionen auf der rechten Seite d¨urfen andere Typargumente haben als das auf der linken Seite:
dataTreea= Leaf a|Branch (Tree (a,a)) Beispielwerte:
tree0,tree1,tree2:: Tree Integer tree0 = Leaf1
tree1 = Branch (Leaf (1,2))
tree2 = Branch (Branch (Leaf ((1,2),(3,4))))
Und wie programmieren wir jetzt auf diesen Typen?
2
Nested Datatypes
Rekursive Positionen auf der rechten Seite d¨urfen andere Typargumente haben als das auf der linken Seite:
dataTreea= Leaf a|Branch (Tree (a,a)) Etwas eing¨angiger modelliert:
newtypeLeaf a= Leaf a dataBrancha = Brancha a
dataTreea = Zeroa|Succ (Tree (Brancha)) tree0,tree1,tree2:: Tree (Leaf Integer)
tree0 = Zero (Leaf1)
tree1 = Succ (Zero (Branch (Leaf1) (Leaf 2)))
tree2 = Succ (Succ (Zero (Branch (Branch (Leaf1) (Leaf2)) (Branch (Leaf3) (Leaf4))))) Und wie programmieren wir jetzt auf diesen Typen?
2
Nested Datatypes
Ben¨otigt polymorphe Rekursion:
mirror:: Tree (Leafa)→Tree (Leafa) mirrort=got id
wherego:: Treeb→(b →b)→Treeb
go(Succt) f = Succ (got (λ(Brancht1 t2)
→Branch (f t2) (f t1))) go(Zerox)f = Zero (f x)
Test:
>mirror tree2
Succ (Succ (Zero (Branch (Branch (Leaf4) (Leaf 3)) (Branch (Leaf2) (Leaf 1))))) Und was ist mitsubst???
3
27. Bundeswettbewerb Informatik Aufgaben 1. Runde
Aufgabe 3: Alle Alpen
Eine Sch¨ulergruppe m¨ochte ein eigenes 2D-Computerspiel realisieren. Lilli ist f¨ur die Generie- rung der Hintergrundbilder zust¨andig. Die Szenerie soll ein Gebirge sein, und Lilli schl¨agt eine einfache Methode zur Beschreibung von Gebirgsz¨ugen vor, n¨amlich als Folge von H¨ohenwerten.
Aber: Ist diese Darstellung variantenreich genug?
Lilli pr¨azisiert ihre Idee: Ein Gebirgszug der L¨ange N sei eine Folge(h0,h1, . . . ,hN)von N+1 nicht-negativen ganzen Zahlen mit h0=hN=0 und|hi−hi−1| ≤1 f¨ur i=1, . . . ,N. Zum Beispiel ist(0,1,1,2,3,2,2,1,0)ein Gebirgszug der L¨ange 8.
h0 h1 h2 h3 h4 h5 h6 h7 h8
Nun m¨ochte sie ein Programm schreiben, das ihr erlaubt, ihre Idee zu pr¨ufen. Versetze dich in ihre Lage und bearbeite wie sie folgende
Aufgabe
1. Schlage eine Darstellung von Gebirgsz¨ugen in der Programmiersprache vor, die du in dieser Aufgabe benutzen m¨ochtest.
2. Schreibe eine Prozedur, die einen Gebirgszug zeichnet. Die Ausgabe soll anschaulich sein, aber keine ¨uberfl¨ussigen Bildelemente enthalten. Zeige deine Ausgabe f¨ur einen Gebirgs- zug der L¨ange 100.
3. Entwirf und implementiere einen Algorithmus, der f¨ur gegebenes N eine sp¨ater zu spe- zifizierende Prozedur P nacheinander mit jedem Gebirgszug der L¨ange N als Argument aufruft.
a) Benutze deinen Algorithmus mit einem geeigneten P, um alle Gebirgsz¨uge der L¨ange 6 auszugeben, und zeige deine Ausgabe.
b) Benutze deinen Algorithmus mit einem anderen P, um die Anzahl der Gebirgsz¨uge der L¨ange 16 zu bestimmen.
4
Challenge
typeAlpen =. . . --
”linear repr¨asentiert“
mountains:: Alpen→[Int] -- injektiv, -- liefert nur
”legale“ Listen, -- liefert alle legalen Listen generate:: Int→[Alpen]
test=sort(map mountains(generate4)) == [[0,0,0,0,0], [0,0,0,1,0], [0,0,1,0,0], [0,0,1,1,0], [0,1,0,0,0], [0,1,0,1,0], [0,1,1,0,0], [0,1,1,1,0], [0,1,2,1,0]]
5
Ein m¨ oglicher Ausgangspunkt
dataAlpen = End|Up Alpen|Equal Alpen|Down Alpen mountains:: Alpen→[Int]
mountainsEnd = [0]
mountains(Upms) =0:map(+1) (mountainsms) mountains(Equalms) =0:mountainsms
mountains(Downms) =0:map(λx →x−1) (mountainsms)
I injektiv?
I liefert nur legale Listen?
I liefert alle legalen Listen?
>mountains(Up (Down (Down (Up End)))) [0,1,0,−1,0]
>mountains(Up End) [0,1]
>mountains(Down End) [0,−1]
6
Ein m¨ oglicher Ausweg?
typeAlpen = Mountains0
dataMountains0 = End|Up Mountains1|Equal Mountains0 dataMountains1 = Up Mountains2|Equal Mountains1
|Down Mountains0
dataMountains2 = Up Mountains3|Equal Mountains2
|Down Mountains1 . . .
mountains:: Alpen→[Int]
mountainsEnd = [0]
mountains(Upms) =0:map(+1) (mountainsms) mountains(Equalms) =0:mountainsms
mountains(Downms) =0:map(λx →x−1) (mountainsms) Hmm, wir glauben nicht wirklich, dass das funktionieren wird, oder?
7
Aber vielleicht ja so?
typeAlpen = Mountains0
dataMountainsk = Up (Mountains (k+1))|Equal (Mountainsk)
|Down (Mountains (k−1))
Aber wie geht man dann mit End um? Und mit Down (Mountains−1)?
Etwa so?
dataMountainsk = Up (Mountains (k+1))|Equal (Mountainsk)
|Down (Mountains (k−1)) --ifk >0
|End --ifk = 0 Oder so?
dataMountainsk = Up (Mountains (k+1))|Equal (Mountainsk)
|if k>0thenDown (Mountains (k−1))else()
|if k ==0thenEndelse()
Und dann vielleicht:
generate:: Int→[Mountainsk]
generate0=if k ==0then[End]else[ ] generaten=mapUp (generate(n−1))
++mapEqual (generate(n−1))
++ifk>0thenmapDown (generate(n−1))else[ ] That way lies madness (or Agda).
8
Aber vielleicht ja so?
typeAlpen = Mountains0
dataMountainsk = Up (Mountains (k+1))|Equal (Mountainsk)
|Down (Mountains (k−1))
Aber wie geht man dann mit End um? Und mit Down (Mountains−1)?
Oder so?
dataMountainsk = Up (Mountains (k+1))|Equal (Mountainsk)
|if k>0thenDown (Mountains (k−1))else()
|if k ==0thenEndelse() Und dann vielleicht:
generate:: Int→[Mountainsk]
generate0=if k ==0then[End]else[ ] generaten=mapUp (generate(n−1))
++mapEqual (generate(n−1))
++ifk>0thenmapDown (generate(n−1))else[ ]
That way lies madness (or Agda). 8
Or, does it (have to)?
Ein”kleines“, zun¨achst unscheinbares Sprachfeature:
{-# LANGUAGE GADTs, KindSignatures #-} Dann m¨oglich zu schreiben:
dataMountains ::∗where End :: Mountains
Up :: Mountains→Mountains Equal :: Mountains→Mountains Down :: Mountains→Mountains
statt: dataMountains = End|Up Mountains|Equal Mountains
|Down Mountains Oder auch, etwa:
dataTree ::∗ → ∗where
{Leaf ::a→Treea; Branch :: Treea→Treea→Treea} statt: dataTreea= Leaf a|Branch (Treea) (Treea)
9
What’s the big deal?
Dann auch m¨oglich:
typeAlpen = Mountains Zero dataMountains ::∗ → ∗where
End :: Mountains Zero
Up :: Mountains (Succk)→Mountainsk Equal :: Mountainsk →Mountainsk
Down :: Mountainsk0 →Mountains (Succk0) deriving instanceShow (Mountainsk)
wobei:
dataZero dataSucck
”Entspricht“:
dataMountainsk = Up (Mountains (k+1))|Equal (Mountainsk)
|Down (Mountains (k−1)) --ifk >0
|End --ifk = 0
10
What’s the big deal?
Dann auch m¨oglich:
typeAlpen = Mountains Zero dataMountains ::∗ → ∗where
End :: Mountains Zero
Up :: Mountains (Succk)→Mountainsk Equal :: Mountainsk →Mountainsk
Down :: Mountainsk0 →Mountains (Succk0) deriving instanceShow (Mountainsk)
wobei:
dataZero dataSucck
Jetzt sind die problematischen (Up (Down (Down (Up End)))) und (Up End) nicht mehr wohlgetypt, und (Down End) hat nicht mehr den Typ Alpen!
10
Konvertierung?
mountains:: Mountainsk→[Int]
mountainsEnd = [0]
mountains(Upms) =0:map(+1) (mountainsms) mountains(Equalms) =0:mountainsms
mountains(Downms) =0:map(λx →x−1) (mountainsms) Was”tats¨achlich“ vor sich geht:
mountains:: Mountainsk→[Int]
mountains(End :: Mountains Zero) = [0]
mountains(Up (ms :: Mountains (Succk))) =0:map(+1) . . . mountains(Equal (ms:: Mountainsk)) =0:mountainsms mountains(Down (ms:: Mountainsk0)) =0:. . .
I injektiv?
I liefert nur legale Listen (bei Aufruf auf Mountains Zero)?
I liefert alle legalen Listen (bei Aufruf auf Mountains Zero)?
11
Aber wie denn nun passende Werte generieren?
Im Prinzip:
generate:: Int→[Mountainsk] generate0|k ≡ Zero = [End]
|k ≡ (Succk0) = [ ]
generaten|k ≡ Zero =mapUp (generate(n−1)) ++mapEqual (generate(n−1))
|k ≡ (Succk0) =mapUp (generate(n−1)) ++mapEqual (generate(n−1)) ++mapDown (generate(n−1)) Mit Typklassenabstraktion!
classGeneratek where
generate:: Int→[Mountainsk] instanceGenerate Zerowhere
generate0= [End]
generaten=mapUp (generate(n−1)) ++mapEqual (generate(n−1))
instanceGeneratek0⇒Generate (Succk0)where generate0= [ ]
generaten=mapUp (generate(n−1)) ++mapEqual (generate(n−1)) ++mapDown (generate(n−1))
>sort(map mountains((generate4) :: [Alpen])) ==. . . True
Challenge solved!
12
Aber wie denn nun passende Werte generieren?
Im Prinzip:
generate:: Int→[Mountainsk] generate0|k ≡ (Succk0) = [ ]
generaten|k ≡ (Succk0) =mapUp (generate(n−1)) ++mapEqual (generate(n−1)) ++mapDown (generate(n−1)) Mit Typklassenabstraktion!
classGeneratek where
generate:: Int→[Mountainsk] instanceGenerate Zerowhere
generate0= [End]
generaten=mapUp (generate(n−1)) ++mapEqual (generate(n−1))
instanceGeneratek0⇒Generate (Succk0)where generate0= [ ]
generaten=mapUp (generate(n−1)) ++mapEqual (generate(n−1)) ++mapDown (generate(n−1))
>sort(map mountains((generate4) :: [Alpen])) ==. . . True
Challenge solved!
12
Aber wie denn nun passende Werte generieren?
Mit Typklassenabstraktion!
classGeneratek where
generate:: Int→[Mountainsk] instanceGenerate Zerowhere
generate0= [End]
generaten=mapUp (generate(n−1)) ++mapEqual (generate(n−1)) instanceGeneratek0⇒Generate (Succk0)where
generate0= [ ]
generaten=mapUp (generate(n−1)) ++mapEqual (generate(n−1)) ++mapDown (generate(n−1))
>sort(map mountains((generate4) :: [Alpen])) ==. . . True
Challenge solved! 12
Popul¨ are GADT-Anwendung: Length-Aware Lists
Analog zum Alpen-Beispiel:
dataVector ::∗ → ∗ → ∗where Nil :: Vector Zeroa
Cons ::a→Vectork a→Vector (Succk)a Beispiele:
empty= Nil
list = Cons3(Cons2Nil)
Automatisch inferierte Typen:
empty:: Vector Zeroa
list :: Vector (Succ (Succ Zero)) Integer
13
Popul¨ are GADT-Anwendung: Length-Aware Lists
Funktionen darauf:
hd:: Vectork a→a hd(Consx xs) =x
hdNil =error"empty vector"
Aber so nat¨urlich kein Gewinn an Ausdruckskraft:
>hd empty
*** Exception: empty vector Stattdessen:
hd:: Vector (Succk)a→a hd(Consx xs) =x
Nun, beihd emptyCompile-Time-Error! Aberhd list okay.
Analog:
tl:: Vector (Succk)a→Vectork a tl(Consx xs) =xs
14
Popul¨ are GADT-Anwendung: Length-Aware Lists
Was ist mit interessanteren Funktionen, zum Beispiel folgender?
appNil ys=ys
app(Consx xs)ys= Consx(appxs ys)
Welchen Typ k¨onnte die denn haben? Sicher irgendeine
”Einschr¨ankung“ von Vectork a→Vectorl a→Vectorm a.
Ein mittlerweile etwas antiquierter Ansatz:
Typklassen ¨uber Phantomtypen.
classAddk l m|k l →mwhere instanceAdd Zerol l where
instanceAddk l m⇒Add (Succk)l (Succm)where app:: Addk l m⇒Vectork a→Vectorl a→Vectorm a Woran erinnert das hoffentlich zumindest einige von Ihnen?
15
Popul¨ are GADT-Anwendung: Length-Aware Lists
Moderner:
type family Add (k::∗) (l::∗) ::∗ type instanceAdd Zero l =l
type instanceAdd (Succk)l = Succ (Addk l) app:: Vectork a→Vectorl a→Vector (Addk l)a appNil ys=ys
app(Consx xs)ys= Consx(appxs ys)
Noch moderner (aber nicht das Ende der Geschichte):
dataNat = Zero|Succ Nat dataVector :: Nat→ ∗ → ∗where
Nil :: Vector Zeroa
Cons ::a→Vectork a→Vector (Succk)a type familyAdd (k:: Nat) (l:: Nat) :: Nat . . .
16
Zur¨ uck zum Beispiel vollst¨ andiger Bin¨ arb¨ aume
Nun:
dataTree :: Nat→ ∗ → ∗where Leaf ::a→Tree Zeroa
Branch :: Treek a→Treek a→Tree (Succk)a tree2:: Tree (Succ (Succ Zero)) Integer
tree2 = Branch (Branch (Leaf1) (Leaf 2)) (Branch (Leaf3) (Leaf 4)) mirror:: Treek a→Treek a
mirror(Brancht1 t2) = Branch (mirrort2) (mirrort1)
mirrort =t
subst::∀k l a b.Treek a→Treel b →Tree (Addk l)b substt t0=got
wherego:: Treek0 a→Tree (Addk0l)b
go(Brancht1t2) = Branch (got1) (got2) go(Leaf ) =t0
17
Und wozu k¨ onnte man GADTs noch gebrauchen?
Denken wir an einen Mini-Interpreter.
Zur Erinnerung:
dataExpr = Lit Int|Add Expr Expr|Sub Expr Expr|Mul Expr Expr expr :: Parser Expr
term :: Parser Expr factor:: Parser Expr nat :: Parser Int eval :: Expr→Int
Angenommen, wir wollen unsere Sprache neben Arithmetik um Boolesche Bedingungen erweitern:
dataExpr = Lit Int|Add Expr Expr|Sub Expr Expr|Mul Expr Expr
|Equal Expr Expr|Not Expr|And Expr Expr
|If Expr Expr Expr Wie garantieren wir Typsicherheit der
”eingebetteten“ Sprache?
Idee:
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 Dann:
eval:: Exprt →t
18
Und wozu k¨ onnte man GADTs noch gebrauchen?
Wie garantieren wir Typsicherheit der
”eingebetteten“ Sprache?
Idee:
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 Dann:
eval:: Exprt →t
18
Zur ¨ Ubung
Definieren Sie f¨ur Alpen bzw. Mountains die Funktionarbitrary (in Instanzen der QuickCheck-Typklasse Arbitrary), so dass etwa folgender Code funktioniert (und etwas Sinnvolles tut):
main=sample$doalps ←arbitrary:: Gen Alpen return(mountainsalps)
19