• Keine Ergebnisse gefunden

Fortgeschrittene Funktionale Programmierung

N/A
N/A
Protected

Academic year: 2022

Aktie "Fortgeschrittene Funktionale Programmierung"

Copied!
26
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Fortgeschrittene Funktionale Programmierung

6. und 7. Vorlesung

Janis Voigtl¨ander

Universit¨at Bonn

Wintersemester 2015/16

(2)

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

(3)

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

(4)

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

(5)

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

(6)

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|hihi−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

(7)

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

(8)

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

(9)

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

(10)

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

(11)

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

(12)

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

(13)

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

(14)

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

(15)

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

(16)

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

(17)

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

(18)

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

(19)

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

(20)

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

(21)

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

(22)

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

(23)

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

(24)

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

(25)

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

(26)

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

Referenzen

ÄHNLICHE DOKUMENTE

TCS | 07 Typisierung | SoSe 2020 2/108 Motivation Typen Typisierungsverfahren..

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 -> * ->

10– 12 MZH 1090 Tarek Online Alexander I Alle Tutorien haben einen Zoom-Raum (für Präsenztutorien als Backup) — siehe Webseite I Diese Woche alle Tutorien online —

catch :: Exception γ⇒ IO α → (γ→ IO α) → IO α try :: Exception γ⇒ IO α → IO (Either γ α) I Faustregel: catch für unerwartete Ausnahmen, try für erwartete I

• Daten sind nullstellige Funktionen, besitzen die Ordnung 0 und heißen Konstanten.. • Die Ordnung einer Funktion

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

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

Haskell hat aber ein mächtiges Typkonzept mit Überladung (Typklassen), das es erlaubt, dass man ganze Zahlkonstanten auch hinschreiben kann, wo Gleitkommazahlen erwartet