Programmieren in Haskell

Werbung
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Programmieren in Haskell
WS 2011/2012
Robert Giegerich
Universität Bielefeld
AG Praktische Informatik
November 19, 2013
Reduktion
RechenStrategien
Wie rechnet Haskell?
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Heute wird geklärt:
Was ist ein “Rechenschritt” in der funktionalen
Programmierung?
In welcher Reihenfolge werden Rechenschritte in Haskell
ausgeführt ...
... und warum gerade in dieser?
Die dabei gegebenen Erklärungen gelten nicht nur für Haskell,
sondern für die funktionale Programmierung im Allgemeinen.
Reduktion
RechenStrategien
Definition von Funktionen durch Gleichungen
Universität Bielefeld
Programmieren
in Haskell
Giegerich
1
2
3
Die Funktion length hatten wir wie folgt definiert:
length
:: [ a ] -> Int
length
[] = 0
length ( x : xs ) = 1 + length xs
Reduktion
Definition von
Funktionen durch
Gleichungen
RechenStrategien
Definition von Funktionen durch Gleichungen
Universität Bielefeld
Programmieren
in Haskell
Giegerich
1
2
3
Die Funktion length hatten wir wie folgt definiert:
length
:: [ a ] -> Int
length
[] = 0
length ( x : xs ) = 1 + length xs
Aber was ist von folgender Definition zu halten?
4
length ( xs ++ ys ) == length xs + length ys
Reduktion
Definition von
Funktionen durch
Gleichungen
RechenStrategien
Definition von Funktionen durch Gleichungen
Universität Bielefeld
Programmieren
in Haskell
Giegerich
1
2
3
Die Funktion length hatten wir wie folgt definiert:
length
:: [ a ] -> Int
length
[] = 0
length ( x : xs ) = 1 + length xs
Aber was ist von folgender Definition zu halten?
4
length ( xs ++ ys ) == length xs + length ys
reicht das, um z.B. length "abraham" auszurechnen?
Reduktion
Definition von
Funktionen durch
Gleichungen
RechenStrategien
Grundbegriffe zu Auswertungs-Strategien:
Was heißt Anwenden von Gleichungen in Formeln
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Redex
=
Stelle in einer Formel, an der die linke Seite
Reduktion
einer Gleichung “passt” (reducible expression)
“passt”
=
die auf der linken Seite verlangten Konstruktoren der Argumente liegen vor
die Substitution der Parameter wird dadurch
definiert
=
“anwenden”
=
Ersetzen des Redex durch die rechte Seite unter
Substitution der Parameter
Anwenden einer Gleichung heisst auch “Reduktion”.
RechenStrategien
Grundbegriffe zu Auswertungs-Strategien:
Was heißt Anwenden von Gleichungen in Formeln
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Redex
=
Stelle in einer Formel, an der die linke Seite
Reduktion
einer Gleichung “passt” (reducible expression)
“passt”
=
die auf der linken Seite verlangten Konstruktoren der Argumente liegen vor
die Substitution der Parameter wird dadurch
definiert
=
“anwenden”
=
Ersetzen des Redex durch die rechte Seite unter
Substitution der Parameter
Anwenden einer Gleichung heisst auch “Reduktion”.
Und was passiert, wenn nirgendwo ein Redex ist?
RechenStrategien
Normalform
Universität Bielefeld
Programmieren
in Haskell
Irgendwann ist eine Formel auch mal ausgerechnet ...
Giegerich
Normalform
Reduktion
Ein Ausdruck ist in Normalform, wenn er keinen Redex enthält.
RechenStrategien
Die erreicht Normalform ist
das Ergebnis, wenn sie nur noch aus Konstruktoren
(einschließlich Zahlen) besteht
ein Fehler, wenn für keine der definierten Funktionen eine
passende Gleichung vorliegt
Rechnen
Rechnen = Reduktion auf Normalform
Terminierung
Universität Bielefeld
Programmieren
in Haskell
... aber nicht bei jeder Rechnung wird eine Normalform
erreicht!
Terminierung
Wenn eine Normalform erreicht wird, terminiert die
Rechnung.
Wenn die Redexe nie ganz verschwinden, terminiert die
Rechnungnicht. Sie divergiert.
Mit einer Rechenstrategie hat man u.U. Einfluss auf die
Terminierung
Giegerich
Reduktion
RechenStrategien
Beispiele
1
head ( x : xs ) = x
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
RechenStrategien
Beispiele
1
head ( x : xs ) = x
Universität Bielefeld
Programmieren
in Haskell
Giegerich
2
3
4
5
6
-- ist anwendbar auf die Formel und ergibt
head (1:2:2:[])
=>
1
head (1:([3 ,4]++[5 ,6]))
=>
1
head (1: ones ) where
ones = 1: ones
=>
1
Reduktion
RechenStrategien
Beispiele
1
head ( x : xs ) = x
Universität Bielefeld
Programmieren
in Haskell
Giegerich
2
3
4
5
6
-- ist anwendbar auf die Formel und ergibt
head (1:2:2:[])
=>
1
head (1:([3 ,4]++[5 ,6]))
=>
1
head (1: ones ) where
ones = 1: ones
=>
1
ist (noch) nicht anwendbar auf
Reduktion
RechenStrategien
Beispiele
1
head ( x : xs ) = x
Universität Bielefeld
Programmieren
in Haskell
Giegerich
2
3
4
5
6
-- ist anwendbar auf die Formel und ergibt
head (1:2:2:[])
=>
1
head (1:([3 ,4]++[5 ,6]))
=>
1
head (1: ones ) where
ones = 1: ones
=>
1
ist (noch) nicht anwendbar auf
7
8
9
10
head ([1 ,3 ,4] ++ [5 ,6])
head ( map (1+) [1 ,2 ,3])
head ones where
ones = 1: ones
Reduktion
RechenStrategien
Beispiele
1
head ( x : xs ) = x
Universität Bielefeld
Programmieren
in Haskell
Giegerich
2
3
4
5
6
-- ist anwendbar auf die Formel und ergibt
head (1:2:2:[])
=>
1
head (1:([3 ,4]++[5 ,6]))
=>
1
head (1: ones ) where
ones = 1: ones
=>
1
ist (noch) nicht anwendbar auf
7
8
9
10
head ([1 ,3 ,4] ++ [5 ,6])
head ( map (1+) [1 ,2 ,3])
head ones where
ones = 1: ones
Es muss erst eine Gleichung für ++, map oder ones angewandt
werden.
Reduktion
RechenStrategien
Ein Schritt weiter ...
Universität Bielefeld
Programmieren
in Haskell
Giegerich
7
8
9
10
7
8
9
10
Ausgangspunkt:
head ([1 ,3 ,4] ++ [5 ,6])
head ( map (1+) [1 ,2 ,3])
head ones where
ones = 1: ones
Es wird eine Gleichung für ++, map bzw. ones angewandt;
nach diesem Schritt ist head ein Redex:
head (1:([3 ,4] ++ [5 ,6]))
=> 1
head ((1+1) : map (1+) [2 ,3])
= > (1+1)
head 1: ones where
=> 1
ones = 1: ones
Reduktion
RechenStrategien
Lage von Redexen in einer Formel
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
Ein Redex ist
innermost, wenn er keinen weiteren Redex enthält,
outermost, wenn er in keinem weiteren Redex enthalten ist,
“zwischendrin”, sonst.
Man sagt auch “innerst” und “äußerst”, aber äußerst ungern.
Es kann ja immer mehrere solche Redexe geben.
RechenStrategien
Beispiel
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
RechenStrategien
Auswertungs-Strategien
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Eine Auswertungs-Strategie legt fest, wie jeweils der nächste
Redex zu wählen ist.
leftmost innermost: immer den Redex wählen, der
innermost ist und am weitesten links steht.
leftmost outermost: immer den Redex wählen, der
outermost ist und am weitesten links steht.
gemischt: man kann sich viele weitere Strategien
vorstellen . . .
Haskell rechnet leftmost-outermost + X,
Lisp, Scheme, Python rechnen leftmost-innermost
Reduktion
RechenStrategien
Menschliches versus maschinelles Rechnen
Mensch und Computer halten sich an die Rechengesetze –
hier: Gleichungen
Computer folgt einer Strategie, die für die
Programmiersprache vorgegeben ist
Der Mensch rechnet tendenziell innermost, insbesondere
der Ungeübte.
Der Erfahrene nutzt Abweichungen von innermost:
Zusammenfassen von mehreren Schritten in einem
(Fehlerquelle!)
Verwendung von mathematischen Gesetzen, die keine
definierenden Gleichungen sind, zur Vereinfachung; z.B.
max (f (x ), f (y )) = f (max (x , y )) wenn f monoton .
Er erkennt die Nichtterminierung und kann anhalten oder
an anderer Stelle weiterrechnen
Das kommt daher, dass der Verstand beim Rechnen weiß, dass
er rechnet.
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
RechenStrategien
if-then-else
Universität Bielefeld
Besonderheit von if_then_else:
Programmieren
in Haskell
Giegerich
Seine definierenden Gleichungen sind
Reduktion
RechenStrategien
if True then x else y
=x
(1)
if False then x else y
=y
(2)
Ein Redex
wird in jedem Fall leftmost outermost berechnet.
Je nach Ergebnis für C wird entweder A oder B berechnet.
if-then-else (2)
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
Diese Regel für if_then_else gilt in allen
Programmiersprachen, auch in den imperativen.
Ohne sie könnte man keine terminierende Rekursion
programmieren:
f ( x ) = if x <= 0 then 42 else 2 + f (x -1)
Innermost-Strategie führt zu endloser Berechnung des
else-Falles.
RechenStrategien
Eigenschaften der Strategien
Universität Bielefeld
Programmieren
in Haskell
Strategie = Regel zur Wahl des nächsten Redex.
1
Ob eine Rechnung terminiert, hängt i.A. von der Strategie
ab.
2
Wenn zwei verschiedene Strategien, angewandt auf die
gleiche Formel, terminieren, liefern sie das gleiche
Ergebnis.
3
Im Fall (2) kann sich der Rechenaufwand stark
unterscheiden.
4
Wenn für eine Formel F irgendeine Strategie terminiert,
dann terminiert für F auch “leftmost outermost”.
5
“leftmost innermost” terminiert seltener als “leftmost
outermost”.
Giegerich
Reduktion
RechenStrategien
Anschauliche Bedeutung
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
innermost: Alle Argumente einer Funktion werden ganz
ausgerechnet, bevor die Funktion “aufgerufen”
wird.
outermost: Die Argumente einer Funktion werden immer nur
so weit ausgerechnet, wie es die Funktion für
ihren nächsten Schritt braucht.
RechenStrategien
Strikte Sprachen
Universität Bielefeld
Programmieren
in Haskell
Die meisten Programiersprachen verwenden für alle Funktionen
f , abgesehen vom if_then_else_ die Strategie leftmost
innermost. Man nennt sie strikt wegen der folgenden
Eigenschaft:
f (.., ⊥, ...) = ⊥
Dabei steht ⊥ für einen Ausdruck, dessen Berechnung nicht
terminiert.
In strikten Sprachen kann man keine unendlichen
Datenstrukturen verwenden – sie würden bei der ersten
Benutzung komplett ausgerechnet.
Giegerich
Reduktion
RechenStrategien
Strikte Sprachen
Universität Bielefeld
Programmieren
in Haskell
Die meisten Programiersprachen verwenden für alle Funktionen
f , abgesehen vom if_then_else_ die Strategie leftmost
innermost. Man nennt sie strikt wegen der folgenden
Eigenschaft:
f (.., ⊥, ...) = ⊥
Dabei steht ⊥ für einen Ausdruck, dessen Berechnung nicht
terminiert.
In strikten Sprachen kann man keine unendlichen
Datenstrukturen verwenden – sie würden bei der ersten
Benutzung komplett ausgerechnet.
Man kann aber outermost dank if_then_else_ simulieren
(Python, .NET)
Giegerich
Reduktion
RechenStrategien
Lazy Evaluation
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
Haskell verwendet lazy evaluation (verzögerte Auswertung), das
ist leftmost outermost + graph reduction
graph reduction ist eine Zusatzregel, die die Duplikation von
noch nicht ausgerechneten Formeln vermeidet.
RechenStrategien
Beispiel zur graph reduction
1
Reine outermost-Strategie hat den Nachteil, dass sie den
Rechenaufwand erhöht, wenn Variablen mehrfach benutzt
werden:
square x = x * x
square
↑
outermost
(3 + 4)
↑
innermost
würde blindlings outermost reduziert zu
(3 + 4) ∗ (3 + 4)
wo nun der Ausdruck (3 + 4) zweimal berechnet werden müsste.
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
RechenStrategien
Beispiel zur graph reduction
Universität Bielefeld
Programmieren
in Haskell
1
2
Stattdessen wird reduziert
square (3+4) = = > x * x
where x = 3+4
worin durch die “Nebenrechnung” 3 + 4 nur einmal berechnet
wird.
Fazit
Lazy evaluation braucht weniger Reduktionen als
innermost-Strategien und terminiert von allen Strategien am
häufigsten.
Allerdings: Lazy evaluation ist aufwendiger zu compilieren als
innermost Strategien.
Giegerich
Reduktion
RechenStrategien
Einfluss von Lazy Evaluation auf den Aufwand
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
RechenStrategien
Unter lazy evaluation kann es sein, dass eine Funktion in einem
bestimmten Kontext nur einen kleinen Teil der scheinbar
benötigten Lösung ausrechnet.
Einfluss von Lazy Evaluation auf den Aufwand
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
Hier die naheliegende Implementierung von minimum:
1
2
3
4
minimum ( a : as ) = min a as where
min a []
= a
min a ( b : x ) = if a <= b then min a x
else min b x
RechenStrategien
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
Hier eine alternative Implementierung, die bequemerweise den
Insertion-Sort benutzt
minimum ( a : as ) = head ( isort ( a : as ))
Spontaner Einwand: Zu aufwändig!! Man muss doch nicht die
ganze Liste sortieren, um das Minimum zu finden!
Sehen wir genauer hin!
RechenStrategien
Einfluss von Lazy Evaluation auf den Aufwand
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
Rechen-
1
2
3
4
5
Die Definition von isort war:
Strategien
isort [] = []
isort ( a : as ) = insert a ( isort as )
insert a [] = [ a ]
insert a ( b : as ) = if a <= b then a : b : as
else b :( insert a as )
Einfluss von Lazy Evaluation auf den Aufwand
Universität Bielefeld
Lazy Evaluation berechnet in einer Formel immer an der
“äußersten” Stelle, an der eine Gleichung anwendbar ist:
Programmieren
in Haskell
Giegerich
Zunächst kann immer nur die Gleichung isort.2 angewandt
werden, am Ende einmal isort.1
head
head
isort
insert
:
=>
a1
insert
:
a1
a2
a2
insert
:
an
an
[]
[]
Reduktion
RechenStrategien
Einfluss von Lazy Evaluation auf den Aufwand
Universität Bielefeld
Programmieren
in Haskell
Danach wird n-mal insert angewandt, beginnend bei an .
Nehmen wir an, a5 ist das kleinste Element. Streng
“outermost” entsteht nun
head
:
=>
a5
insert
a1
innerhalb des Kastens
könnte man weiterrechnen, aber das wäre nicht
“outermost”.
insert
a2
insert
a n−1
[a n]
Giegerich
Reduktion
RechenStrategien
Einfluss von Lazy Evaluation auf den Aufwand
head braucht nicht mehr als den obersten Konstruktur (:), um
sein Ergebnis zu liefern
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Reduktion
head
=>
a5
:
a5
Es werden ≈ 2 · n Gleichungen angewandt. Der Rest der
Operationen für das komplette Sortieren steckt im Kasten und
wird hier nicht benötigt!
RechenStrategien
Fazit
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Durch Lazy Evaluation müssen erheblich weniger Reduktionen
durchgeführt werden als z.B. durch die leftmost-innermost
Strategie.
Was “erheblich” ist, siehe A&D Kap. Effizienzanalyse.
Reduktion
RechenStrategien
Fazit
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Durch Lazy Evaluation müssen erheblich weniger Reduktionen
durchgeführt werden als z.B. durch die leftmost-innermost
Strategie.
Was “erheblich” ist, siehe A&D Kap. Effizienzanalyse.
Rechnen mit unendlichen Datenstrukturen ist nur dank Lazy
Evaluation möglich.
Reduktion
RechenStrategien
Fazit
Universität Bielefeld
Programmieren
in Haskell
Giegerich
Durch Lazy Evaluation müssen erheblich weniger Reduktionen
durchgeführt werden als z.B. durch die leftmost-innermost
Strategie.
Was “erheblich” ist, siehe A&D Kap. Effizienzanalyse.
Rechnen mit unendlichen Datenstrukturen ist nur dank Lazy
Evaluation möglich.
Denkt nochmal über die Definition von primes nach!
Reduktion
RechenStrategien
Herunterladen