Praktische Informatik 3: Funktionale Programmierung Vorlesung 2 vom 09.11.2020: Funktionen
Christoph Lüth
Wintersemester 2020/21
11:51:10 2021-02-22 1 [38]
Fahrplan
ITeil I: Funktionale Programmierung im Kleinen IEinführung
IFunktionen
IAlgebraische Datentypen ITypvariablen und Polymorphie IFunktionen höherer Ordnung I IRekursive und zyklische Datenstrukturen IFunktionen höherer Ordnung II
ITeil II: Funktionale Programmierung im Großen ITeil III: Funktionale Programmierung im richtigen Leben
PI3 WS 20/21 2 [38]
Inhalt und Lernziele
IDefinition vonFunktionen ISyntaktischeFeinheiten IBedeutung von Haskell-Programmen
IStriktheit
ILeben ohne Variablen IFunktionen statt Schleifen IZahllose Beispiele Lernziele
Wir wollen einfache Haskell-Programme schreiben können, eine Idee von ihrer Bedeutung bekommen, und ein Leben ohne veränderliche Variablen führen.
PI3 WS 20/21 3 [38]
I. Definition von Funktionen
PI3 WS 20/21 4 [38]
Definition von Funktionen
IZwei wesentlicheKonstrukte:
IFallunterscheidung IRekursion Satz
Fallunterscheidung und Rekursion auf natürlichen Zahlen sindTuring-mächtig.
IFunktionen müssenpartiellsein können.
IInsbesondere nicht-terminierende Rekursion
IFragen: wie schreiben Funktionen in Haskell auf (Syntax), und was bedeutet das (Semantik)?
PI3 WS 20/21 5 [38]
Haskell-Syntax: Charakteristika
ILeichtgewichtig IWichtigstes Zeichen:
IFunktionsapplikation:f a IKlammern sindoptional IHöchstePriorität (engste Bindung)
IAbseitsregel: Gültigkeitsbereich durch Einrückung IKeineKlammern({. . .}) (optional)
IAuch in anderenSprachen(Python, Ruby)
PI3 WS 20/21 6 [38]
Haskell-Syntax: Funktionsdefinition
Generelle Form:
ISignatur:
max :: Int→Int→Int IDefinition:
max x y=ifx<y thenyelse x IKopf, mit Parametern
IRumpf(evtl. länger, mehrere Zeilen)
ITypischesMuster: Fallunterscheidung, dann rekursiver Aufruf IWas gehört zum Rumpf (Geltungsberereich)?
PI3 WS 20/21 7 [38]
Haskell-Syntax I: Die Abseitsregel
Funktionsdefinition:
f x1 x2 x3...xn=e
IGültigkeitsbereichder Definition vonf:
alles, was gegenüberfeingerücktist.
IBeispiel:
f x=hier faengts an und hier gehts weiter
immer weiter
g y z=und hier faengt was neues an IGilt auchverschachtelt.
IKommentare sindpassiv(heben das Abseits nicht auf).
PI3 WS 20/21 8 [38]
Haskell-Syntax II: Kommentare
IPro Zeile: Ab−−bis Ende der Zeile
f x y=irgendwas −− und hier der Kommentar!
IÜber mehrere Zeilen: Anfang{−, Ende-}
{−Hier faengt der Kommentar an erstreckt sich ueber mehrere Zeilen
bis hier −}
f x y=irgendwas IKann geschachtelt werden.
PI3 WS 20/21 9 [38]
Haskell-Syntax III: Bedingte Definitionen
IStatt verschachtelter Fallunterscheidungen . . . f x y=ifB1then Pelse
ifB2then Qelse R . . .bedingte Gleichungen: f x y
| B1=P
| B2=Q
IAuswertung der Bedingungen von oben nach unten IWenn keine Bedingung wahr ist:Laufzeitfehler! Deshalb:
| otherwise=R
PI3 WS 20/21 10 [38]
Haskell-Syntax IV: Lokale Definitionen
ILokale Definitionen mitwhereoderlet: f x y
| g=P y
| otherwise=f x where y=M
f x=N x
f x y= lety=M
f x=N x in ifgthen P y
else f x If,y, . . . werdengleichzeitigdefiniert (Rekursion!)
INamenf,yund Parameter (x)überlagernandere IEs gilt dieAbseitsregel
IDeshalb:Aufgleiche Einrückungder lokalen Definition achten!
PI3 WS 20/21 11 [38]
Jetzt seit ihr dran!
Übung 2.1: Syntax
In dem Beispielprogramm auf der vorherigen Folie, welche der Variablenf,xundyauf den rechten Seiten wird wo gebunden?
Lösung:
PI3 WS 20/21 12 [38]
II. Auswertung von Funktionen
PI3 WS 20/21 13 [38]
Auswertung von Funktionen
IAuswertung durchAnwendungvon Gleichungen IAuswertungsrelations→t:
IAnwendung einer Funktionsdefinition
IAnwendung von elementaren Operationen (arithmetisch, Zeichenketten) IFrage: spielt dieReihenfolgeeine Rolle?
PI3 WS 20/21 14 [38]
Auswertung von Ausdrücken
inc :: Int→Int inc x=x+ 1
dbl :: Int→Int dbl x=2∗x IReduktion voninc (dbl (inc 3))
IVonaußennachinnen(outermost-first):
inc (dbl (inc 3)) →dbl (inc 3)+ 1
→2∗(inc 3)+ 1
→2∗(3+ 1)+ 1→2∗4+1→8+1→9 IVoninnennachaußen(innermost-first):
inc (dbl (inc 3)) →inc (dbl (3+1))→inc (dbl 4)
→inc (2∗4)→inc 8
→8+1→9
PI3 WS 20/21 15 [38]
Auswertung von Ausdrücken
inc :: Int →Int inc x=x+ 1
dbl :: Int →Int dbl x=2∗x IVolle Reduktion voninc (dbl (inc 3)):
inc (dbl (inc 3)) inc (2* (inc 3)
dbl (inc 3)+1 inc (dbl (3+1)) 2*(inc 3)+1 dbl (3+1)+1 inc (2*(3+1) inc (dbl 4)
2*(3+1)+1 dbl 4 +1 inc (2* 4)
2*4+1 inc 8
8+1 9
PI3 WS 20/21 16 [38]
Konfluenz
IEs kommt immer das gleiche heraus?
ISei→∗ die Reduktion in null oder mehr Schritten.
Definition (Konfluenz)
→∗ istkonfluentgdw:
Für aller,s,tmits←∗r→∗ tgibt esuso dasss→∗ u←∗t.
PI3 WS 20/21 17 [38]
Konfluenz
IWenn wir von Laufzeitfehlern abstrahieren, gilt:
Theorem (Konfluenz)
Die Auswertungsrelation→∗ für funktionale Programme istkonfluent.
IBeweisskizze:
Seif x=Eunds→∗t:
f s ∗
-f t
E
"
s x
#
∗
?
∗ -E
"
t x
#
∗
?
PI3 WS 20/21 18 [38]
Auswirkung der Auswertungsstrategie
IAuswertungsstrategie ist also egal?
IBeispiel:
repeat :: Int→String→String repeat n s=ifn==0then""
elses++repeat (n-1) s
undef :: String undef=undef
IAuswertung vonrepeat 0 undef: repeat 0 undef repeat 0 undef repeat 0 undef
""
repeat 0 undef repeat 0 undef
"" repeat 0 undef repeat 0 undef repeat 0 undef
"" repeat 0 undef repeat 0 undef repeat 0 undef repeat 0 undef
"" repeat 0 undef repeat 0 undef repeat 0 undef ...
Ioutermost-firstterminiert Iinntermost-first terminiertnicht
PI3 WS 20/21 19 [38]
Termination und Normalform
Definition (Termination)
→istterminierendgdw. eskeine unendlichenKetten gibt:
t1→t2→t3→. . .tn→. . .
Theorem (Normalform)
Sei→∗konfluent und terminierend, dann wertet jeder Term zu genau einerNormalformaus, die nicht weiter ausgewertet werden kann.
IDaraus folgt:terminierendefunktionale Programme werten unter jeder Auswertungsstragie jeden Ausdruck zum gleichen Wert aus (der Normalform).
PI3 WS 20/21 20 [38]
Auswirkung der Auswertungsstrategie
IAuswertungsstrategie nur fürnicht-terminierendeProgramme relevant.
ILeider ist nicht-Terminationnötig(Turing-Mächtigkeit) IGibt es einesemantischeCharakterisierung?
IAuswertungsstrategie und Parameterübergabe:
IOutermost-first entsprichtcall-by-need,verzögerteAuswertung.
IInnermost-first entsprichtcall-by-value,strikteAuswertung
PI3 WS 20/21 21 [38]
Zum Mitdenken. . .
Übung 2.2:
Warumentspricht outermost-first call-ny-need und innermost-first call-by-value?
Lösung:Der Aufruf einer Funktionf x=Eentspricht hier der Ersetzung der linken Seitef durch die rechte SeiteE, mit den Parameternxentsprechend ersetzt.
Wenn wir beispielsweise Auswertung des Ausdrucksdbl (dbl (dbl (7+3)))betrachten, dann wird innermost-first zuerst7+3reduziert, danndbl 10etc, d.h. jeweils dieArgumente der Funktion — Funktionen bekommen nur Werte übergeben.
Bei outermost-first wird zuerst das äußerstedblreduziert, was dem Aufruf der Funktiondbl mit dem nicht ausgewerteten Argumentdbl (dbl (7+3))entspricht (verzögerte Auswertung).
PI3 WS 20/21 22 [38]
III. Semantik und Striktheit
PI3 WS 20/21 23 [38]
Bedeutung (Semantik) von Programmen
IOperationaleSemantik:
IDurch denAusführungsbegriff IEin Programmist, was estut.
IIn diesem Fall:→ IDenotationelleSemantik:
IProgramme werden aufmathematische Objekteabgebildet (Denotat).
IFür funktionale Programme:rekursivdefinierte Funktionen Äquivalenz von operationaler und denotationaler Semantik
SeiPein funktionales Programm,→∗ die dadurch definierte Reduktion, und [[P]] das Denotat.
Dann gilt für alle Ausdrücketund Wertev
t→∗ v ⇐⇒[[P]](t) =v
PI3 WS 20/21 24 [38]
Striktheit
Definition (Striktheit)
Funktionfiststrikt ⇐⇒ Ergebnis ist undefiniert, sobald ein Argument undefiniert ist.
IDenotationelleEigenschaft (nicht operational) IHaskell ist nachSprachdefinition nicht-strikt
Irepeat 0 undefmuss""ergeben.
IMeistenImplementationennutzenverzögerte Auswertung
IAndere Programmiersprachen:
IJava, C, etc. sindcall-by-value(nach Sprachdefinition) und damitstrikt IFallunterscheidung istimmernicht-strikt, Konjunktion und Disjunktion meist auch.
PI3 WS 20/21 25 [38]
Jetzt seit ihr dran!
Übung 2.3: Strikte Fallunterscheidung
Warum ist Fallunterscheidung immer nicht-strikt, auch in Java?
Lösung:Betrachte
y=x==0 ? -1 : 100/x; if (x==0) { y=-1;
} else{ y=100/x;
}
Wäre die Fallunterscheidung strikt, würden erstbeideFälle ausgewertet; es wäre nicht mehr möglich, die Auswertung undefinierter Ausdrücke abzufangen. Das gleich gilt für das Programm rechts.
PI3 WS 20/21 26 [38]
IV. Leben ohne Variablen
PI3 WS 20/21 27 [38]
Rekursion statt Schleifen
Fakultät imperativ:
r=1;
while(n>0) { r=n∗r;
n=n- 1;
}
Fakultät rekursiv:
fac’ n r= ifn≤0then r else fac’ (n-1) (n∗r) fac n=fac’ n 1 IVeränderliche Variablen werden zu Funktionsparametern IIteration (while-Schleifen) werden zu Rekursion IEndrekursion verbraucht keinen Speicherplatz
PI3 WS 20/21 28 [38]
Rekursive Funktionen auf Zeichenketten
ITest auf die leere Zeichenkette:
null :: String→Bool null xs= xs==""
IKopf und Rest einer nicht-leeren Zeichenkette (vordefiniert):
head :: String→Char tail :: String→String
DEMO
PI3 WS 20/21 29 [38]
Suche in einer Zeichenkette
ISuche nach einem Zeichen in einer Zeichenkette:
count1 :: Char→String→Int IIn einem leeren String: kein Zeichen kommt vor
IAnsonsten: Kopf vergleichen, zum Vorkommen im Rest addieren count1 c s=
ifnull sthen 0
else ifhead s==cthen 1+ count1 c (tail s) else count1 c (tail s)
IÜbung: wie formuliere ichcountmit Guards? (Lösung in den Quellen)
PI3 WS 20/21 30 [38]
Suche in einer Zeichenkette
IEndrekursiv:
count3 c s=count3’ c s 0 count3’ c s r=
ifnull sthenr
elsecount3’ c (tail s) (if head s==cthen1+r elser) IEndrekursiv mit lokaler Definition
count4 c s=count4’ s 0where count4’ s r=
ifnull sthenr
elsecount4’ (tail s) (if head s==cthen1+r elser)
DEMO
PI3 WS 20/21 31 [38]
Strings konstruieren
I:hängt Zeichen vorne an Zeichenkette an (vordefiniert) (:) :: Char→String→String
IEs gilt: Wenn not (null s), dannhead s : tail s==s IMit(:)wird(++)definiert:
(++) :: String→String→String xs++ ys
| null xs =ys
| otherwise=head xs : (tail xs++ys) Iquadratkonstruiert ein Quadrat aus Zeichen:
quadrat :: Int→Char→String
quadrat n c=repeat n (repeat n (c: "")++"\n")
DEMO
PI3 WS 20/21 32 [38]
Strings analysieren
IWarum immer nur Kopf/Rest?
ILetztes Zeichen (dual zuhead):
last1 :: String→Char
last1 s=if null sthenlast1 s
else ifnull (tail s) thenhead s elselast1 (tail s)
IBesser: mit Fehlermeldung last :: String→Char last s
| null s=error "last:␣empty␣string"
| null (tail s)=head s
| otherwise =last (tail s)
PI3 WS 20/21 33 [38]
Strings analysieren
IAnfang der Zeichenkette (dual zutail):
init :: String→String init s
| null s=error "init:␣empty␣string" −− nicht s
| null (tail s)=""
| otherwise =head s : init (tail s)
IDamit: Wennnot (null s), danninit s++ (last s: "")==s
PI3 WS 20/21 34 [38]
Strings analysieren: das Palindrom
IPalindrom: vorwärts und rückwärts gelesen gleich.
IRekursiv:
IAlle Wörter der Länge 1 oder kleiner sind Palindrome
IFür längere Wörter: wenn erstes und letztes Zeichen gleich sind und der Rest ein Palindrom.
IErste Variante:
palin1 :: String→Bool palin1 s
| length s≤1 =True
| head s==last s=palin1 (init (tail s))
| otherwise =False
DEMO
PI3 WS 20/21 35 [38]
Strings analysieren: das Palindrom
IProblem: Groß/Kleinschreibung, Leerzeichen, Satzzeichen irrelevant.
IDaher: nicht-alphanumerische Zeichen entfernen, alles Kleinschrift:
clean :: String→String clean s
| null s=""
| isAlphaNum (head s)=toLower (head s) : clean (tail s)
| otherwise=clean (tail s) IErweiterte Version:
palin2 s=palin1 (clean s)
DEMO
PI3 WS 20/21 36 [38]
Fortgeschritten: Vereinfachung von palin1
IDas hier ist nicht so schön:
palin1 s
| length s≤1 =True
| head s==last s=palin1 (init (tail s))
| otherwise =False IWas steht da eigentlich:
palin1’ s=iflength s≤1then True
else ifhead s==last sthen palin1’ (init (tail s)) else False
IDamit:
palin3 s=length s≤1 | | head s==last s && palin3 (init (tail s)) ITerminiert nur wegen Nicht-Striktheit von| |
PI3 WS 20/21 37 [38]
Zusammenfassung
IBedeutungvon Haskell-Programmen:
IAuswertungsrelation→
IAuswertungsstrategien: innermost-first, outermost-first IAuswertungsstrategie für terminierende Programme irrelevant IStriktheit
IHaskell istspezifiziertals nicht-strikt IMeist implementiert durch verzögerte Auswertung ILebenohne Variablen:
IRekursion statt Schleifen IFunktionsparameter statt Variablen INächste Vorlesung: Datentypen
PI3 WS 20/21 38 [38]