Informatik 2 - Konzepte der Programmierung

Werbung
8
Listenverarbeitung
8 · Listenverarbeitung
I
Funktionen, die Listen als Argumente besitzen, orientieren sich oft an
der rekursiven Struktur von Listen (cf. Seite 146):
Rekursionsabbruch Das Argument ist die leere Liste [] :: [α], oder
Rekursion das Argument ist von der Form x:xs mit x :: α und xs :: [α].
I
Die Definition einer listenverarbeitenden Funktion f :: [α] → β nutzt
daher oft eine entsprechende Fallunterscheidung
f [] = z
f (x : xs) = c (h x) (t xs)
wobei
• z :: β das Ergebnis bei Rekursionsabbruch darstellt, und
• im Rekursionsfall
I h :: α → γ auf den Kopf (head), und
I t :: [α] → δ auf den Rest (tail) des Arguments angewandt werden,
I während c :: γ → δ → β das Ergebnis beider Aufrufe kombiniert.
Dabei ruft t typischerweise f selbst rekursiv auf.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
208
8 · Listenverarbeitung
Beispiel Die Summe über eine Liste
1
2
3
sum :: [Integer] -> Integer
sum [] = 0
sum (x:xs) = x + sum xs
1
2
> sum [1..10]
55
I
Dabei sind z = 0, h = id, t = sum und c = (+).
I
Die Identitätsfunktion id :: α → α ist Teil der standard prelude und hat
die Definition id = \x -> x, also id = λx. x
Beispiel map f wendet die Funktion f auf jedes Element einer Liste an.
1
2
3
map :: (α -> β) -> [α] -> [β]
map f [] = []
map f (x:xs) = f x : map f xs
1
2
> map (+1) [1..10]
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
I
Dabei sind z = [], h = f, t = map f und c = (:).
I
Man sagt auch: Eine Funktion über eine Liste mappen.
Frage Welche Funktion f erhält man mittels
z = False, h = (e ==), t = f e und c = ||?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
209
8 · Listenverarbeitung
Haskells Standard Prelude definiert nützliche Funktionen über Listen:
1
1
2
head (x:_)
= x
2
3
3
4
tail (_:xs)
= xs
init [x]
init (x:xs)
= []
= x : init xs
4
5
5
6
7
9
7
13
last [x]
last (_:xs)
= x
= last xs
length []
length (_:xs)
= 0
= 1 + length xs
9
10
11
14
15
16
19
20
23
[] — analog: drop
[]
x : take (n-1) xs
18
= []
= reverse xs ++ [x]
concat []
= []
concat (xs:xss) = xs ++ concat xss
19
20
21
reverse []
reverse (x:xs)
[]
++ ys = ys
(x:xs) ++ ys = x : xs ++ ys
16
17
take n _ | n <= 0 =
take _ []
=
take n (x:xs)
=
filter _ [] = []
filter p (x:xs)
| p x
= x : filter p xs
| otherwise = filter p xs
13
15
21
22
12
14
(x:_) !! 0
= x
(_:xs) !! n | n>0 = xs !! (n-1)
17
18
zip = zipWith (,)
8
11
12
_
= []
[]
= []
(y:ys)
x y : zipWith f xs ys
6
8
10
zipWith _ []
zipWith _ _
zipWith f (x:xs)
= f
22
23
dropWhile _ [] = [] — analog: takeWhile
dropWhile p xs@(x:xs')
| p x
= dropWhile p xs'
| otherwise = xs
Typen? Funktionsweise? Welche Funktionen sind nur partiell definiert (Fehler bei falschen Arg.)?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
210
8 · Listenverarbeitung
Haskells Standard Prelude definiert nützliche Funktionen über Listen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
head :: [a] -> a
head (x:_)
= x
tail :: [a] -> [a]
tail (_:xs)
= xs
init :: [a] -> [a]
init [x]
= []
init (x:xs)
= x : init xs
last :: [a] -> a
last [x]
= x
last (_:xs)
= last xs
length :: [a] -> Int
length []
= 0
length (_:xs)
= 1 + length xs
(!!) :: [a] -> Int -> a
(x:_) !! 0
= x
(_:xs) !! n | n>0 = xs !! (n-1)
take :: Int -> [a] -> [a]
take n _ | n <= 0 = [] — analog: drop
take _ []
= []
take n (x:xs)
= x : take (n-1) xs
reverse :: [a] -> [a]
reverse []
= []
reverse (x:xs)
= reverse xs ++ [x]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
zipWith
zipWith
zipWith
zipWith
:: (a->b->c) -> [a]->[b]->[c]
_ []
_
= []
_ _
[]
= []
f (x:xs) (y:ys)
= f x y : zipWith f xs ys
zip :: [a] -> [b] -> [(a, b)]
zip = zipWith (,)
filter :: (a -> Bool) -> [a] -> [a]
filter _ [] = []
filter p (x:xs)
| p x
= x : filter p xs
| otherwise = filter p xs
(++) :: [a] -> [a] -> [a]
[]
++ ys = ys
(x:xs) ++ ys = x : xs ++ ys
concat :: [[a]] -> [a]
concat []
= []
concat (xs:xss) = xs ++ concat xss
dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile _ [] = [] — analog: takeWhile
dropWhile p xs@(x:xs')
| p x
= dropWhile p xs'
| otherwise = xs
Die tatsächliche Implementierung weicht teilweise ab. Partiell: head, tail, init, last, (!!)
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
211
8 · Listenverarbeitung
Erinnerung: Operatoren
Auf den letzten Folien wurden zwei Operatoren definiert.
I
(!!) :: [a] -> Int -> a
1
2
Positionaler Zugriff auf Listenelemente
(x:_) !! 0
= x
(_:xs) !! n | n>0 = xs !! (n-1)
(++) :: [a] -> [a] -> [a] Konkatenation zweier Listen
I
1
2
[]
++ ys = ys
(x:xs) ++ ys = x : xs ++ ys
Operatoren unterscheiden sich von “normalen” (Präfix)-Funktionen nur
syntaktisch, ansonsten verhalten sich beide in allen Kontexten gleich (cf.
Seite 138). Man könnte sogar Präfix- und Infix-Schreibweise in der gleichen
Definition mischen – das hilft aber nicht der Lesbarkeit:
1
2
3
(#@!) :: Integer -> [a] -> [a]
0 #@! _ = []
(#@!) n xs = xs ++ ((n-1) #@! reverse xs)
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
212
8 · Listenverarbeitung
Was ist Foldable? Sollten das nicht Listen sein?
Moderne Versionen des GHCi zeigen für manche Funktionen
überraschend einen Typ mit Foldable statt mit Listen an:
I
1
2
Prelude> :t length
length :: Foldable t => t a -> Int
I
Das verstehen wir leider erst später, cf. Seite 307.
I
Vorerst stellen wir uns vor es ginge um Listen29 , d.h., wir lesen
Foldable t ⇒ ... t α ...
einfach als
... [α] ...
ersetzen also jedes t α durch [α].
Beispiele Wir verwenden zunächst nur:
null :: [α] → Bool
length :: [α] → Int
concat :: [[α]] → [α]
29 Tatsächlich
statt
statt
statt
null :: Foldable t ⇒ t α → Bool
length :: Foldable t ⇒ t α → Int
concat :: Foldable t ⇒ t [α] → [α]
geht es um allgemeinere Datenstrukturen, die wir aber wohl nicht besprechen werden.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
213
8 · Listenverarbeitung
8.1
foldr und foldl · 8.1
foldr und foldl
Das besprochene Rekursionsschema ist in der Standard Prelude mit zwei
Funktionen, foldr (fold right) und foldl (fold left), implementiert.
foldr (auch bekannt unter dem Namen reduce)
I
“reduziert” eine Liste vom Typ [α] zu einem Wert des Typs β.
I
Informell gilt:
(dabei ist ein Infix-Operator des Typs α → β → β)
foldr () z [x1 , x2 , ..., xn ]
≡
x1 (x2 (· · · (xn z) · · · ))
Damit ist foldr :: (α → β → β) → β → [α] → β.
I Eselsbrücken: Die Klammerung ist rechts-assoziativ, und das z erscheint ganz rechts.
Ein mögliche Definition von foldr ist:
1
2
3
foldr :: (α -> β -> β) -> β -> [α] -> β
foldr () z []
= z
foldr () z (x:xs) = x foldr () z xs
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
214
8 · Listenverarbeitung
foldr und foldl · 8.1
Alternativ lässt sich der Effekt von foldr damit auch wie folgt illustrieren:
foldr () z
(x1 : (x2 : (...( xn : [] )...)))
↓ ↓ ↓ ↓
↓ ↓ ↓
= (x1 (x2 (...( xn z )...)))
I
Die Funktion foldr ersetzt also alle Listen-Konstruktoren, und zwar
• : durch , und
• [] durch z.
Beispiel Viele Standardfunktionen
implementieren:
sum
=
product =
concat
=
and
=
or
=
Michael Grossniklaus · DBIS
lassen sich einfach mittels foldr
foldr (+) 0
foldr ? ?
foldr ? ?
foldr ? ?
foldr ? ?
Informatik 2 · Sommer 2017
215
8 · Listenverarbeitung
Lösung:
I
foldr und foldl · 8.1
=
=
=
=
=
sum
product
concat
and
or
foldr (+)
0
foldr (*)
1
foldr (++)
[]
foldr (&&) True
foldr (||) False
Mit der Behauptung sum so definieren zu können, behaupte ich eine
Äquivalenz von sum von Folie 209 und foldr (+) 0:
sum ≡ foldr (+) 0
(Die entsprechenden Beweise ggf. später in den Übungen.)
~
Obacht Wir hatten vereinbart (cf. Seite 116), dass zwei λ-Ausdrücke
e1 , e2 äquivalent sind, gdw. sie zur gleichen Normalform m reduziert werden
können, d.h.:
e1 ≡ e2
⇔
∃m. e1 _∗ m ∧ e2 _∗ m
Frage Was ist z.B. mit sum ≡ foldr (+) 0 — gibt es so ein m?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
216
8 · Listenverarbeitung
foldr und foldl · 8.1
Gleichheit von Funktionen
Das Extensionalitätsprinzip
Antwort Leider nicht. Ohne Argumente können wir nicht weit reduzieren.
I
I
Die Aussage dass sich zwei Funktionen gleich verhalten macht aber
natürlich trotzdem Sinn.
Deswegen erweitern wir unsere Definition von Äquivalenz etwas:
Definition Äquivalenz von Funktionen
Zwei Funktionen f , g heißen äquivalent gdw. sie für das gleiche
Argument äquivalente Ergebnisse liefern, d.h.:
f ≡g
⇔
∀x. f x ≡ g x
Für die Äquivalenz auf der rechten Seite bemühen wir diese Definition
erneut, oder verwenden die bekannte Äquivalenz von Seite 116.
I
Dieses Prinzip ist in der Mengenlehre als Extensionalitätsprinzip
bekannt. Man sagt auch: f und g sind extensional gleich.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
217
8 · Listenverarbeitung
foldr und foldl · 8.1
Monoid
Nochmal:
sum
product
concat
and
or
≡
≡
≡
≡
≡
foldr (+)
0
foldr (*)
1
foldr (++)
[]
foldr (&&) True
foldr (||) False
Beobachtung All diesen Beispielen ist gemeinsam, dass assoziativ ist
und sich z bzgl. dieser Operation neutral verhält.
Definition Monoid
Eine Menge M mit einer binären Verknüpfung ⊕ :: M × M → M heißt
Monoid, genau dann, wenn
(Dabei sei M das Universum der Quantoren)
I
die Operation ⊕ assoziativ ist,
I
und es ein Neutralelement e gibt.
∀a b c. a ⊕ (b ⊕ c) ≡ (a ⊕ b) ⊕ c
∃e. ∀a. e ⊕ a ≡ a ≡ a ⊕ e
Übrigens: Falls :: α → β → β assoziativ ist, gilt bereits α = β. Warum?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
218
8 · Listenverarbeitung
I
foldr und foldl · 8.1
Natürlich kann foldr auch auf Strukturen angewendet werden, die keinen
Monoid bilden:
Beispiel
1
2
3
4
filter p
length
reverse
takeWhile p
=
=
=
=
5
6
7
foldr (\x xs -> if p x then x:xs else xs) []
foldr (\_ n -> 1+n) 0
foldr (\x xs -> xs ++ [x]) []
foldr (#) []
where
x # xs | p x
= x:xs
| otherwise = []
Damit könnte takeWhile (<3) [1..4] etwa wie folgt reduziert werden:
1
takeWhile (<3) [1..4]
2
3
4
5
6
Michael Grossniklaus · DBIS
_
foldr (#) [] (1 : (2 : (3 :
_ 1 # (2 # (3 #
_ 1 : (2 # (3 #
_ 1 : (2 : (3 #
_ 1 : (2 : [])
_ [1,2]
Informatik 2 · Sommer 2017
(4
(4
(4
(4
:
#
#
#
[]))))
[])))
[])))
[])))
219
8 · Listenverarbeitung
foldr und foldl · 8.1
Linksassoziative Variante von foldr
Analog zu foldr gibt es die vordefinierte Funktion foldl.
foldl (also fold left)
I klammert die Listenelemente während der Reduktion nach links.
I Informell gilt:
(dabei ist ein Infix-Operator des Typs β → α → β)
foldl () z [x1 , x2 , ..., xn ]
≡
(· · · ((z x1 ) x2 ) · · · ) xn
Damit ist foldl :: (β → α → β) → β → [α] → β
I Eselsbrücken: Die Klammerung ist links-assoziativ, und das z erscheint ganz links.
Ein mögliche Definition von foldl ist:
1
2
3
foldl :: (β -> α -> β) -> β -> [α] -> β
foldl () z [] = z
foldl () z (x:xs) = foldl () (z x) xs
Hier übernimmt z die Rolle eines akkumulierenden Parameters, in dem
das Endergebnis aufgesammelt wird.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
220
8 · Listenverarbeitung
foldr und foldl · 8.1
Beispiel Eine praktische Anwendung von foldl ist die Funktion pack, die
eine Liste von Ziffern [xn−1 , xn−2 , ..., x0 ] mit xi ∈ {0...9} in den durch sie
“dargestellten” Wert transformiert:
n−1
X
xk · 10k
k=0
1
2
3
pack :: [Integer] -> Integer
pack xs = foldl (#) 0 xs
where n # x = 10 * n + x
Eine Beispiel-Reduktion sähe folgendermaßen aus:
1
pack [1, 9, 8, 4]
2
3
4
5
6
Michael Grossniklaus · DBIS
_
foldl (#) 0 (1 : (9 : (8 : (4
_
(((0 # 1) # 9)
_
((1 # 9)
_
(19
_
_
Informatik 2 · Sommer 2017
: []))))
# 8) # 4
# 8) # 4
# 8) # 4
198 # 4
1984
221
8 · Listenverarbeitung
foldr und foldl · 8.1
Das 1. Dualitätstheorem
I
Auch hier gilt: Falls :: β → α → β assoziativ ist, dann ist α = β.
I
Dann haben foldl und foldr den gleichen Typ:
foldr, foldl :: (α → α → α) → α → [α] → α
Es gilt sogar:
Satz 1. Dualitätstheorem
Falls (⊕, α) einen Monoid mit Neutralelement e bilden, dann gilt:
foldr (⊕) e
≡
foldl (⊕) e
Beweis Evtl. Übung, nach Kapitel über Induktion (cf. Seite 233).
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
222
8 · Listenverarbeitung
foldr und foldl · 8.1
Unmittelbare Konsequenz:
I
Die Funktionen von Seite 217 können wir auch mit foldl bauen.
sum
product
concat
and
or
I
≡
≡
≡
≡
≡
foldl (+)
0
foldl (*)
1
foldl (++)
[]
foldl (&&) True
foldl (||) False
Es bleibt zu klären, welche Variante effizienter ist. Das machen wir später
genauer.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
223
8 · Listenverarbeitung
foldr und foldl · 8.1
Das 2. Dualitätstheorem
Auch ohne einen Monoid sind foldr und foldl eng verwandt:
Satz 2. Dualitätstheorem
Falls für alle x, y , z geeigneten Typs gilt
x (y z)
x y
≡ (x y ) z
und
≡ y x
dann gilt
foldr ()
≡
foldl ()
Beweis Übung, nach Kapitel über Induktion (cf. Seite 233).
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
224
8 · Listenverarbeitung
foldr und foldl · 8.1
Beispiel
length = foldr (λ n. 1 + n) 0
length0 = foldl (λn . 1 + n) 0
I
Diese beiden Funktionen sind äquivalent.
I
Die Variante mit foldl kann effizienter ausgewertet werden.
(Dazu müssen wir aber noch mehr über Auswertestrategien wissen, cf. später.)
Beweis mit dem 2. Dualitätstheorem. Zu zeigen:
foldr (λ n. 1 + n) 0
|
{z
}
≡
I
Zeigen x z ≡ z x:
≡
≡
x z
1+z
z x
Michael Grossniklaus · DBIS
I
foldl (λn . 1 + n) 0
|
{z
}
Zeigen x (y z) ≡ (x y ) z:
≡
≡
x (y z)
1 + (y z)
1 + (1 + y )
Informatik 2 · Sommer 2017
≡
≡
(x y ) z
1 + (x y )
1 + (1 + y )
225
8 · Listenverarbeitung
foldr und foldl · 8.1
Das 3. Dualitätstheorem
Schließlich gilt noch:
Satz 3. Dualitätstheorem
Sei reverse wie auf Seite 210 definiert, und sei flip f x y = f y x. Dann gilt
für alle , z und xs geeigneten Typs:
foldr () z xs
≡
foldl (flip ()) z (reverse xs)
Mit anderen Worten: Mit x y ≡ y x gilt:
foldr () z xs
≡
foldl () z (reverse xs)
Beweis Kann man als (aufwändige) Übung machen, nach Kapitel über
Induktion (cf. Seite 233).
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
226
8 · Listenverarbeitung
foldr und foldl · 8.1
Unendliche Listen
~
Vorsicht Eine Bemerkung zu foldl/foldr auf unendlichen Listen:
foldl () z (x:xs)
= foldl () (z x) xs
foldr () z (x:xs)
= x foldr () z xs
I
Für die nicht-leere Liste ruft sich foldl sofort rekursiv selbst auf, und das
Ergebnis des rekursiven Aufrufs ist das Ergebnis der Funktion.
I
Bei foldr hängt das Ergebnis hingegen von ab. Der rekursive Aufruf
von foldr ist nur Argument von .
⇒ Konsequenz:
• foldl terminiert sicher nicht auf unendlichen Listen!
• Bei foldr entscheidet der Operator ob Rekursion stattfindet, und kann
vor Ende der Liste abbrechen. (cf. Seite 219, takeWhile)
Beispiel head ≡ foldr const ⊥ funktioniert auf unendlichen Listen
(mit const = \x y -> x aus der Prelude und ⊥ ∼
= undefined)
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
227
8 · Listenverarbeitung
foldr und foldl · 8.1
Anmerkungen
I
Die Ersetzung der Konstruktoren eines Datentyps (hier für Listen, also
cons (:) und nil []) durch Operatoren bzw. Werte ist ein Prinzip, das
sich auch für andere konstruierte Datentypen sinnvoll anwenden
lässt.
I
Im Gegensatz zu foldr ersetzt foldl nicht nur die Konstruktoren, sondern
ändert die Klammerung der rechtstief konstruierten Liste. Insofern
nimmt es eine Sonderrolle ein.
Beide Punkte werden wir später wieder aufgreifen.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
228
8 · Listenverarbeitung
8.2
Effizienz · 8.2
Effizienz
Die Anzahl der Reduktionen bei der Reduktion eines Ausdrucks auf seine
Normalform ist in FPLs ein naheliegendes Maß für die Komplexität einer
Berechnung.
Beispiel Die Länge der ersten Argumentliste
bestimmt die Anzahl der Reduktionen der
Listen-Konkatenation ++.
[3,2] ++ [1]
I
++.2 steht dabei für die 2. Zeile der
Definition von ++, cf. Seite 210.
_
I
Für die Auswertung von xs ++ ys mit
length xs _ n werden n Reduktionen via
++.2, gefolgt von einer Reduktion via ++.1
benötigt.
_
I
Die letzte Zeile ist lediglich eine Änderung
der Schreibweise.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
_
≡
++.2
3:([2] ++ [1])
++.2
3:(2:([] ++ [1]))
++.1
3:(2:[1])
[3,2,1]
229
8 · Listenverarbeitung
Effizienz · 8.2
Beispiel Ähnliche Überlegungen gelten für reverse, das in seiner
Definition ++ nutzt:
reverse [1,2,3]
_
_
_
_
_
_
_
Gilt length xs _ n, dann benötigt
reverse xs
reverse.2
reverse [2,3] ++ [1]
reverse.2
(reverse [3] ++ [2]) ++ [1]
I
n Reduktionen via reverse.2,
I
gefolgt von einer Reduktion via
reverse.1,
I
gefolgt von
reverse.2
((reverse [] ++ [3]) ++ [2]) ++ [1]
reverse.1
1 + 2 + ... + n =
(([] ++ [3]) ++ [2]) ++ [1]
++.1
([3] ++ [2]) ++ [1]
++.2, ++.1
[3,2] ++ [1]
++.2, ++.2, ++.1
n · (n + 1)
2
Reduktionen, um mittels ++ die
Konkatenationen auszuführen.
Damit ist die Anzahl der
Reduktionen in O(n2 ).
[3,2,1]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
230
8 · Listenverarbeitung
Effizienz · 8.2
Listeninvertierung in linearer Zeit
Eine Liste lässt sich aber durchaus in linearer Zeit (proportional zur
Listenlänge n) reversieren:
1
2
3
4
5
rev :: [α] -> [α]
rev xs = shunt [] xs
where
shunt ys []
= ys
shunt ys (x:xs) = shunt (x:ys) xs
I
I
Tatsächlich reversiert shunt ys xs nicht nur die Liste xs, sondern
konkateniert diese zusätzlich auch noch mit ys.
Das erste Argument ys von shunt wird akkumulierender Parameter
genannt:
• Zwischenergebnisse der Berechnung werden an die nächste Rekursion
weitergegeben.
• Am Ende der Rekursion enthält ys das Ergebnis (oder einen Teil davon).
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
231
8 · Listenverarbeitung
Effizienz · 8.2
Beispiel rev xs benötigt lineare Anzahl (proportional zu (length xs))
Reduktionen
rev [1,2,3]
_
_
_
_
_
rev.1
shunt [] [1,2,3]
shunt.2
shunt [1] [2,3]
shunt.2
shunt [2,1] [3]
shunt.2
shunt [3,2,1] []
shunt.1
[3,2,1]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
232
8 · Listenverarbeitung
8.3
I
Induktion über Listen · 8.3
Induktion über Listen
Dank referenzieller Transparenz kann man Behauptungen wie
rev ≡ reverse
relativ einfach beweisen.
I
Beweise über listenverarbeitende Funktionen können häufig mittels
Induktion über Listen, analog zu Induktionsbeweisen für Behauptungen
über Elemente aus N, geführt werden:
1. Induktionsanfang (aka. Induktionsverankerung).
I
Beweise die Aussage mit der leeren Liste [].
2. Induktionsschritt von xs zu x : xs.
I
Übung
Dabei wird die Induktionshypothese (die Aussage mit einer beliebigen, festen
Liste xs) verwendet, um die Aussage mit x : xs für alle x zu zeigen.
Für alle Listen xs gilt xs ++ [ ] ≡ xs.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
233
8 · Listenverarbeitung
Induktion über Listen · 8.3
Beispiel Beweisen rev xs ≡ reverse xs für alle Listen xs.
I
Da rev xs _ shunt [ ] xs (cf. Seite 231), genügt es, die allgemeinere
Behauptung zu zeigen:
shunt ys xs
≡
reverse xs ++ ys
für alle xs, ys :: [α]
Induktionsverankerung Sei xs = [ ] und ys :: [α] beliebig.
shunt ys [ ] ≡ ys
≡ [ ] ++ ys
≡ reverse [ ] ++ ys
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
(shunt.1)
(++.1)
(reverse.1)
235
8 · Listenverarbeitung
Induktion über Listen · 8.3
Induktionshypothese Für ein beliebiges, festes xs :: [α] und für alle ys :: [α]
gelte:
shunt ys xs ≡ reverse xs ++ ys
Induktionsschritt Von xs zu x : xs. Seien x :: α und ys :: [α].
Mit dem xs aus der Induktionshypothese gilt:
shunt ys (x : xs)
≡
≡
≡
≡
≡
≡
shunt (x : ys) xs
reverse xs ++ (x : ys)
reverse xs ++ (x : ([ ] ++ ys))
reverse xs ++ ([x] ++ ys)
(reverse xs ++ [x]) ++ ys
reverse (x : xs) ++ ys
(shunt.2)
(Hypothese)
(++.1 rückw.)
(++.2 rückw.)
(++ assoziativ)
(reverse.2)
Übung Die hier verwendete Annahme über die Assoziativität von ++ müssen wir
ebenfalls noch beweisen: xs ++ (ys ++ zs) ≡ (xs ++ ys) ++ zs. Auch hier führt
Listeninduktion über den Parameter zum Ziel, über den die Rekursion der betrachteten
Funktion ++ formuliert ist, also xs.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
236
8 · Listenverarbeitung
8.4
Programm-Synthese · 8.4
Programm-Synthese
Bei der Beweisführung über Programme werden Eigenschaften eines
gegebenen Programms Schritt für Schritt nachvollzogen und dadurch
bewiesen (s. rev und reverse).
Programm-Synthese kehrt dieses Prinzip um:
I
Gegeben ist eine formale Spezifikation eines Problems,
I
gesucht ist ein problemlösendes Programm, das durch schrittweise
Umformung der Spezifikation gewonnen (synthetisiert) wird.
Wenn die Transformationen diszipliniert vorgenommen werden, kann die
Synthese als Beweis dafür gelesen werden, dass das Programm die
Spezifikation erfüllt (der Traum aller Software-Ingenieure).
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
237
8 · Listenverarbeitung
Programm-Synthese · 8.4
Beispiel Die Funktion init der standard prelude bestimmt das initiale
Segment ihres Listenargumentes, also etwa
init [1..10] _ [1, 2, 3, 4, 5, 6, 7, 8, 9].
I
Damit wäre eine naheliegende Spezifikation für init die folgende:
init xs = take (length xs - 1) xs
wobei xs endlich und nicht leer
“Nimm alle Elemente von xs, aber nicht das letzte Element”
I
Die Synthese versucht eine effizientere Variante von init abzuleiten
(die Spezifikation wäre ja prinzipiell schon ausführbar, traversiert xs zur
Berechnung des Ergebnisses aber zweimal).
Für die Synthese instantiieren wir xs
1. mit [x] und
2. mit x1 : x2 : xs.
Jede nichtleere Liste besitzt die eine oder die andere Form.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
238
8 · Listenverarbeitung
Programm-Synthese · 8.4
Fall 1 [x]
init [x]
=
— Instanziierung
Spezifikation
take (length [x] − 1) [x]
=
length, Arithmetik
take 0 [x]
=
take.1
[]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
239
8 · Listenverarbeitung
Programm-Synthese · 8.4
Fall 2 x1 : x2 : xs
init (x1 : x2 : xs)
=
— Instanziierung
Spezifikation
take (length (x1 : x2 : xs) − 1) (x1 : x2 : xs)
=
length, Arithmetik
take (length xs + 1) (x1 : x2 : xs)
=
take.3
x1 : take (length xs) (x2 : xs)
=
=
length.2, Arithmetik
x1 : take (length (x2 : xs) − 1) (x2 : xs)
x1 : init (x2 : xs)
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
240
8 · Listenverarbeitung
Programm-Synthese · 8.4
Zusammenfassen der beiden so erhaltenen Gleichungen ergibt
1
2
3
init :: [a] -> [a]
init [x]
= []
init (x1:x2:xs) = x1 : init (x2:xs)
Weitere Verbesserungen
1
2
3
4
I
Das wiederholte Zerlegen und Zusammensetzen der Liste x2 : xs kann
man sich sparen.
I
Für die leere Liste geben wir einen brauchbaren Fehler aus.
init
init
init
init
:: [a] -> [a]
[x]
= []
(x:xs) = x : init xs
[]
= error "init: empty list"
Übung Versuchen Sie Ihren Haskell-Code durch simple
Äquivalenzumformungen zu verbessern. Manchmal gewinnt man dabei
überraschende Einsichten.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
241
8 · Listenverarbeitung
8.5
List Comprehensions · 8.5
List Comprehensions
List Comprehensions sind vor allem in modernen FPLs als eine alternative
Notation für Operationen auf Listen verbreitet30 .
I
Die Notation mittels Set Comprehension31 ist aus der Mathematik
(Mengenlehre) bekannt. Die Idee dabei:
Term Prädikat+
• Beschreibt eine Menge, deren Elemente jeweils von Term beschrieben werden,
• unter den durch die Prädikate bestimmten Bedingungen.
I
List Comprehensions erweitern die Ausdruckskraft der Sprache nicht,
erlauben aber oft eine kompakte, leicht lesbare und elegante Notation
von Listenoperationen.
Wir werden eine Abbildung auf den Haskell-Kern besprechen.
30 Miranda™ (Dave Turner, 1976) sah als erste FPL List Comprehensions syntaktisch
31 aka. “set-builder notation”. Einen deutschen Begriff scheint’s nicht zu geben.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
vor.
242
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Die Menge aller natürlichen geraden Zahlen kann durch eine
set comprehension kompakt notiert werden:
{n | n ∈ N, n mod 2 = 0}
Eine entsprechende List Comprehension (die unendliche Liste aller geraden
Zahlen) wird syntaktisch ganz ähnlich notiert:
[ n | n <- [0 .. ], n `mod` 2 == 0 ]
Beispiel Die Standardfunktionen map und filter sind mittels List
Comprehensions ohne die sonst notwendige Rekursion formulierbar:
1
2
map :: (α -> β) -> [α] -> [β]
map f xs = [ f x | x <- xs ]
Michael Grossniklaus · DBIS
1
2
filter :: (α -> Bool) -> [α] -> [α]
filter p xs = [ x | x <- xs, p x ]
Informatik 2 · Sommer 2017
243
8 · Listenverarbeitung
List Comprehensions · 8.5
Syntax der List Comprehension
Die allgemeine Form einer List Comprehension ist
[ e | q1 , q2 , ..., qn ]
wobei
I
der Kopf e ein beliebiger Ausdruck ist, und
I
die Qualifier qi (mit n ≥ 1), eine von drei Formen besitzen:
Generator pi <- ei , wobei ei :: [αi ], und pi ein Pattern für Werte des Typs
αi ist — schreiben salopp pi :: αi .
Prädikat qi :: Bool.
lokale Bindung let { pi1 = ei1 ; pi2 = ei2 ... }. Dabei sind die eij beliebige
Ausdrücke, und die pij entsprechende Patterns.
Beispiel von vorhin: [ n | n <- [0 .. ], n `mod` 2 == 0 ] .
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
244
8 · Listenverarbeitung
List Comprehensions · 8.5
ListComp
→
[ Expr | Qual (, Qual)∗ ]
Qual
→
|
|
Pattern <- Expr
— Pattern :: α, Expr :: [α]
Expr
— Expr :: Bool
— cf. Seite 172
let { Pattern = Expr (; Pattern = Expr)∗ }
— Zusammenfassung
Semantik der List Comprehension — in Worten
I
Ein Generator qi = pi <- ei versucht das Pattern pi der Reihe nach
gegen die Elemente der Liste ei zu matchen.
• Für jeden erfolgreichen Match werden die nachfolgenden Qualifier
qi+1 , ..., qn ausgewertet.
• Die durch den Match gebundenen Variablen des Patterns pi sind in den
nachfolgenden Qualifiern sichtbar und an entsprechende Werte gebunden.
I
Eine lokale Bindung (let) kann ebenfalls Variablen an Werte binden.
I
Jede Bindung wird solange nach rechts propagiert, bis ein Prädikat
unter ihr zu False evaluiert wird.
I
Der Kopf e wird für alle Bindungen ausgewertet, die alle Prädikate
passieren konnten.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
245
8 · Listenverarbeitung
List Comprehensions · 8.5
Entsprechend dieser Semantik wird also in [ e | p1 <- e1 , p2 <- e2 ]
I
zuerst über die Domain e1 des Generators p1 <- e1 iteriert, und dann
I
für jeden Match von p1 über die Domain e2 des Generators p2 <- e2 .
Dies trifft die Intuition der aus der Mengenlehre bekannten Set
Comprehension:
[ (x,y) | x <- [x1 , x2 ], y <- [y1 , y2 ] ]
_
Michael Grossniklaus · DBIS
[(x1 ,y1 ), (x1 ,y2 ), (x2 ,y1 ), (x2 ,y2 )]
Informatik 2 · Sommer 2017
246
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Elegante (aber ineffiziente) Variante von Quicksort als 2-Zeiler
1
2
3
4
5
qsort :: (α -> α -> Bool) -> [α] -> [α]
qsort _
[]
= []
qsort (<?) (x:xs) = qsort (<?) [ y | y <- xs, y <? x ]
++ [x] ++
qsort (<?) [ y | y <- xs, not (y <? x) ]
Beachte: In der split-Phase dieser Implementation wird die Liste xs jeweils
(unnötigerweise) zweimal durchlaufen.
Beispiel Matrix über Typ α als Liste von Zeilenvektoren (wiederum
Listen). Bestimme ersten Spaltenvektor:
1
2
firstcol :: [[α]] -> [α]
firstcol m = [ e | (e:_) <- m ]
firstcol nutzt die Möglichkeit, in Generatoren Patterns zu spezifizieren.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
247
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Alle Permutationen einer Liste xs
1. Die leere Liste [ ] hat sich selbst als einzige Permutation.
2. Wenn xs nicht leer ist, wähle ein Element a aus xs und stelle a den
Permutationen der Liste xs ohne a voran.
3. Führe 2. für jedes Element der Liste xs aus.
1
2
3
perms :: [Integer] -> [[Integer]]
perms [] = [[]]
perms xs = [ a:p | a <- xs, p <- perms $ xs \\ [a] ]
4
5
6
> perms [2, 3]
[[2, 3], [3, 2]]
I
I
Dabei entfernt die Listendifferenz xs \\ ys alle Elemente von ys aus der
Liste xs, etwa: [1, 2, 1, 2, 3] \\ [2, 5] _ [1, 1, 3].
Wie könnte man \\ implementieren?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
248
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Berechne alle Pythagoräischen Dreiecke, in denen keine Seite
länger als n ist.
1
pyth n = [ (a,b,c) | c <- [1..n], a <- [1..c], b <- [1..a], a^2 + b^2 == c^2 ]
2
3
4
> pyth 20
[(4,3,5),(8,6,10),(12,5,13),(12,9,15),(15,8,17),(16,12,20)]
Beispiel Was berechnet die folgende Funktion bar? Wie lautet ihr Typ?
1
bar xs = [ x | [x] <- xs ]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
249
8 · Listenverarbeitung
Beispiel
1
2
List Comprehensions · 8.5
“Join” zwischen zwei Listen bzgl. eines Prädikates p
join :: (α -> β -> γ) -> (α -> β -> Bool) -> [α] -> [β] -> [γ]
join f p xs ys = [ f x y | x <- xs, y <- ys, p x y ]
Den “klassischen relationalen Join” R1 1fst=fst R2 auf binären Relationen
Ri , erhält man dann durch
1
2
3
4
5
foo = join
(\x y -> (fst x, snd x, snd y))
(\x y -> fst x == fst y)
[(1, "John"), (2, "Jack"), (3, "Bonnie")]
[(2, "Ripper"), (1, "Doe"), (3, "Parker"), (2, "Dalton"), (1, "Cleese")]
6
7
8
9
Prelude> foo
[ (1, "John", "Doe"), (1, "John", "Cleese"), (2, "Jack", "Ripper")
, (2, "Jack", "Dalton"), (3, "Bonnie", "Parker")]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
250
8 · Listenverarbeitung
List Comprehensions · 8.5
Operationale Semantik für List Comprehensions
Die Semantik der List Comprehensions, welche wir vorhin (cf. Seite 245)
eher durch “hand-waving” erklärt haben, lässt sich – ganz ähnlich wie bei
der β-Reduktion des λ-Kalküls – durch Reduktionsregeln formal erklären.
Definition Semantik der List Comprehension, ohne Pattern Matching
Sei e ein Haskell-Ausdruck, v Variable, qs eine Sequenz32 von Qualifiern.
Die folgenden Regeln reduzieren jeweils den ersten Qualifier:
○
1
[ e | v <- [], qs ] _ []
[ e | v <- (x : xs), qs ] _ [ e | qs ][v x]
2
++ [ e | v <- xs, qs ] ○
[ e | False, qs ] _ []
[ e | True, qs ] _ [ e | qs ]
[ e | ] _ [ e ]
32 qs
○
3
○
4
○
5
ist keine Haskell-Liste, sondern ein Konstrukt der Meta-Ebene, cf. Seite 245.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
251
8 · Listenverarbeitung
List Comprehensions · 8.5
I
Die ersten beiden Reduktionsregeln reduzieren einen Generator über einer
1 bzw. nichtleeren ○
2 Liste.
leeren ○
I
3 und ○
4 testen Prädikate.
Regeln ○
I
5 ist anwendbar, sobald die Sequenz der Qualifier vollständig
Regel ○
reduziert wurde.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
252
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Reduktion von [ x^2 | x <- [1,2,3], odd x ]
[ x^2 | x <- [1,2,3], odd x ]
_
=
_
_
_
_
2
verwenden ○
[ x^2 | odd x ][x 1] ++ [ x^2 | x <- [2,3], odd x ]
|
{z
}
A
[ 1^2 | odd 1 ] ++ A
[ 1^2 | True ] ++ A
4
verwenden ○
[ 1^2 | ] ++ A
5
verwenden ○
[1^2] ++ A
A
z
}|
{
1 : [ x^2 | x <- [2,3], odd x ]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
253
8 · Listenverarbeitung
List Comprehensions · 8.5
1 : [ x^2 | x <- [2,3], odd x ]
_
=
_
_
_
_
_
=
2
verwenden ○
1 : [ x^2 | odd x ][x
2] ++ [ x^2 | x <- [3], odd x ]
|
{z
}
1 : [ 2^2 | odd 2 ] ++ B
B
1 : [ 2^2 | False ] ++ B
3
verwenden ○
1 : [] ++ B
B
z
}|
{
1 : [ x^2 | x <- [3], odd x ]
2
verwenden ○
1 : [ x^2 | odd x ][x
3] ++ [ x^2 | x <- [], odd x ]
1
links wie gehabt (_ 9), und rechts verwenden wir ○
1 : 9 : []
[1, 9]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
254
8 · Listenverarbeitung
List Comprehensions · 8.5
Abbildung von List Comprehensions auf den Haskell-Kern
I
Prinzipiell erlaubt das System der eben besprochenen Reduktionsregeln,
List Comprehensions auf in Haskell vordefinierte Funktionen
zurückzuführen.
I
Im Folgenden betrachten wir ein Übersetzungsschema J·K, das List
Comprehensions aus Haskell-Code entfernt33 :
J Code mit List Comprehension K = Code ohne List Comprehension
I
J·K kann vom Compiler auf Haskell-Quellcode angewandt werden, um
äquivalenten Code ohne List-Comprehensions zu erhalten.
33 Hier
verwenden wir semantische Klammern etwas anders als gewohnt.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
255
8 · Listenverarbeitung
1
2
List Comprehensions · 8.5
Dabei basiert das Schema J·K auf der Funktion concatMap:
concatMap :: (α -> [β]) -> [α] -> [β]
concatMap f = foldr (\x xs -> f x ++ xs) []
Frage Was tut diese Funktion?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
256
8 · Listenverarbeitung
1
2
List Comprehensions · 8.5
concatMap :: (α -> [β]) -> [α] -> [β]
concatMap f = foldr (\x xs -> f x ++ xs) []
3
4
5
> concatMap (replicate 3) "hello"
"hhheeellllllooo"
Antwort concatMap f xs wendet die Funktion f auf jedes Element von xs
an. Dabei gibt f jeweils eine Liste zurück, welche von concatMap
konkateniert werden.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
257
8 · Listenverarbeitung
List Comprehensions · 8.5
Übersetzungsschema J·K
immer noch ohne Pattern Matching
Sei e ein Ausdruck, v eine Variable, b ein Boolescher Ausdruck, und qs
wieder eine Sequenz von Qualifiern.
J[ e | v <- xs, qs ]K = concatMap (λv . J[ e | qs ]K) JxsK
○
1
2
J[ e | b, qs ]K = if JbK then J[ e | qs ]K else [] ○
J[ e | ]K = [ JeK ]
JeK = Wende J·K rekursiv auf alle nicht-primitiven
○
3
○
4
Teilausdrücke von e an.
I
I
1 : Generatoren; ○
2 : Prädikate)
Wieder wird die Sequenz der Qualifier (○
3 den Fall ohne Qualifier behandeln kann.
reduziert, bis ○
4 steigt rekursiv im AST ab, um evtl. weitere List
Regel ○
Comprehensions zu übersetzen.
2 , statt einfach b zu schreiben?
Frage Wozu brauchen wir JbK in Regel ○
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
258
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Übersetzung von [ x^2 | x <- [1..5], odd x ]
=
=
=
=
J[ x^2 | x <- [1..5], odd x ]K
○
1
concatMap (λx. J[ x^2 | odd x ]K) J[1..5]K
○
4
concatMap (λx. J[ x^2 | odd x ]K) [1..5]
○
2
concatMap (λx. if Jodd xK then J[ x^2 | ]K else []) [1..5]
○
3 und 2 × ○
4
concatMap (λx. if odd x then [ x^2 ] else []) [1..5]
Frage Bisher haben wir Pattern Matching in unserem
Übersetzungsschema J·K nicht berücksichtigt. Wo müssen wir
nachbessern?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
259
8 · Listenverarbeitung
List Comprehensions · 8.5
Pattern Matching in List Comprehensions
Beispiel Was tut die Funktion heads? Was ist der Typ?
1
heads xs = [ y | (y:_) <- xs ]
Übersetzen heads mit dem Übersetzungsschema J·K:
=
=
=
Jλxs. [ y | (y:_) <- xs ]K
○
4
λxs. J[ y | (y:_) <- xs ]K
○
1 , behandeln Pattern wie Variable
λxs. concatMap λ(y:_). J[ y | ]K
○
3,○
1
JxsK
λxs. concatMap λ(y:_). [y] xs
]
η concatMap λ(y:_). [y]
Frage Gilt heads ≡ concatMap λ(y:_). [y]
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
?
260
8 · Listenverarbeitung
List Comprehensions · 8.5
Antwort Nein: Wenn der Pattern Match gegen (y:_) fehlschlägt, wird das
Programm abgebrochen!
1
2
3
4
> heads [[1],[2,3],[4,5,6]]
[1,2,4]
> concatMap (\(y:_)-> [y]) [[1],[2,3],[4,5,6]]
[1,2,4]
5
6
7
8
9
> heads [[1],[],[4,5,6]]
[1,4]
> concatMap (\(y:_)-> [y]) [[1],[],[4,5,6]]
[1*** Exception: <interactive>:23:12-23: Non-exhaustive patterns in lambda
Frage An welcher Stelle können wir das fixen?
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
261
8 · Listenverarbeitung
List Comprehensions · 8.5
Definition Übersetzungsschema J·K mit Pattern Matching
Sei e ein Ausdruck, p ein Pattern, v eine neue Variable, b ein Boolescher
Ausdruck, und qs wieder eine Sequenz von Qualifiern.
J[ e | p <- xs, qs ]K = concatMap λv . case v of
p → J[ e | qs ]K _ → []
JxsK
○
1
2
J[ e | b, qs ]K = if JbK then J[ e | qs ]K else [] ○
J[ e | ]K = [ JeK ]
JeK = Wende J·K rekursiv auf alle nicht-primitiven
○
3
○
4
Teilausdrücke von e an.
I
I
I
Variable v matcht gegen jeden Wert aus JxsK (cf. Seite 156),
case prüft dann ob v auf Pattern p matcht (cf. Seite 162),
für fehlgeschlagene Matches werden keine Ergebnisse erzeugt.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
262
8 · Listenverarbeitung
List Comprehensions · 8.5
Beispiel Nochmal: heads xs = [ y | (y:_) <- xs ]
Übersetzen heads mit dem Übersetzungsschema J·K:
=
=
Jλxs. [ y | (y:_) <- xs ]K
○
4
λxs. J[ y | (y:_) <- xs ]K
○
1 , diesmal richtig
λxs. concatMap λv . case v of
(y:_) → J[ y | ]K _ → []
JxsK
○
3,○
1
=
λxs. concatMap λv . case v of { (y:_) → [y]; _ → [] } xs
]
η concatMap λv . case v of { (y:_) → [y]; _ → [] }
Jetzt gilt:
heads ≡ concatMap λv . case v of { (y:_) → [y]; _ → [] }
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
263
8 · Listenverarbeitung
List Comprehensions · 8.5
Bibliographie
Richard Bird and Philip Wadler:
Introduction to Functional Programming using Haskell,
Prentice Hall International, Series in Computer Science, 1998.
Jeroen Fokker:
Functional Programming,
Department of Computer Science, Utrecht University, 1995.
http://www.staff.science.uu.nl/~fokke101/courses/fp-eng.pdf
Torsten Grust and Marc H. Scholl:
How to Comprehend Queries Functionally,
Journal of Intelligent Information Systems, vol. 12, p. 191–218, 1999.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
264
Herunterladen