Vorlesung: Praxis der Funktionalen Programmierung


fold: Herleitung

Wir hatten bereits die Anzahl der Elemente einer Liste gezählt
length :: [a] -> Integer
length [] = 0
length (x : xs) = 1 + length xs
Wir können auch die Zahlen aus einer Liste summieren
sum :: [ Integer ] -> Integer
sum [] = 0
sum (x : xs) = x + sum xs
oder feststellen, ob alle Elemente einer booleschen Liste wahr sind
and :: [ Bool ] -> Bool
and [] = True
and (x : xs) = x && and xs
Alle diese Funktionen folgen wieder einem gemeinsamen Schema. Es ist aber nicht so einfach wie ein map, da die elementweisen Rechnungen doch vom Ergebnis der bisherigen Rechungen abhängen.

fold: Definition

Wir finden jedoch für jedes obige f einen Startwert x0 sowie eine zweistellige Funktion g mit
f [x1, x2, ... xn ] = g x1 (g x2 (... (g xn x0) ...))
Das Rekursionsschema heißt fold-right:
foldr
foldr f x0 []       = x0
foldr f x0 (x : xs) = f x (foldr f x0 xs)
und dann gilt
length = foldr ( \ x y -> 1 + y ) 0
sum    = foldr (+)  0
and    = foldr (&&) True

fold:Beispiele

Auch einige Funktionen, die erstmal nicht so aussehen, sind folds.
head :: [a] -> a
head [] = error "head von leerer Liste"
head (x : xs) = x
Als fold sieht es so aus
head = foldr const (error "head von leerer Liste")
Die nächste Funktion ist recht praktisch
filter :: (a -> Bool) -> [a] -> [a]
filter p [] = []
filter p (x : xs) = 
    if p x then x : filter p xs else filter p xs
mit der äquivalenten Definition
filter p = foldr ( \ x ys -> if p x then x : ys else ys ) []
Jetzt wirds interessant: wir wollen reverse als foldr schreiben. Wir hatten ja schon gesehen, daß die naive Implementierung zu lange dauert
reverse = foldr ( \ x ys -> ys ++ [x] ) []
Der Trick ist, daß wir nicht die umgedrehte Liste erzeugen, sondern eine Funktion, die, wenn wir sie auf die leere Liste anwenden, die umgedrehte Liste ergibt. Wir suchen also
funreverse :: [a] -> ([a] -> [a])
funreverse xs []  ==  reverse xs
Für funreverse können wir leicht eine Definition durch foldr angeben:
funreverse = foldr ( \ x f -> f . (x :) ) id
damit haben wir
reverse xs = foldr ( \ x f -> f . (x :) ) id xs []
Wir können das noch ein bißchen verzaubern und noch einige Variablennamen entfernen
reverse = \ xs ->  foldr ( \ x f -> f . (x :) ) id xs []
        = \ xs ->  flip (foldr ( \ x f -> f . (x :) ) id) [] xs
        = flip (foldr ( \ x f -> f . (x :) ) id ) []
	= flip (foldr ( \ x f -> flip (.) (x :) f ) id) []
	= flip (foldr ( \ x -> flip (.) (x :) ) id) []

Übungen

Schreibe die Funktion
take :: Int -> [a] -> [a]
take 0 xs = []
take n (x : xs) = x : take (n-1) xs
als foldr. Hinweis: definiere stattdessen "flip take".

Es gibt auch ein fold von links,

foldl g x0 [ x1, x2, ..., xn ] = g(g(g(g x0 x1) x2 ...) xn
Vervollständige die Definition
foldl g x0 [] = x0
foldl g x0 (x : xs) =
und prüfe
reverse = foldl (flip (:)) []
Stelle foldl durch foldr dar, und andersherum!

Lösung (teilweise)


best viewed with any browser


http://www.informatik.uni-leipzig.de/~joe/ mailto:joe@informatik.uni-leipzig.de