Eine andere Art, darüber nachzudenken: foldr existiert, weil das folgende rekursive Muster häufig verwendet wird:
-- Example 1: Sum up numbers
summa :: Num a => [a] -> a
summa [] = 0
summa (x:xs) = x + suma xs
Das Produkt von Zahlen oder auch die Umkehrung einer Liste sieht strukturell sehr ähnlich aus wie die vorherige rekursive Funktion:
-- Example 2: Reverse numbers
reverso :: [a] -> [a]
reverso [] = []
reverso (x:xs) = x `op` reverso xs
where
op = (\curr acc -> acc ++ [curr])
Die Struktur in den obigen Beispielen unterscheidet sich nur durch den Ausgangswert ( 0
für Summa und []
für Reverso) zusammen mit dem Operator zwischen dem ersten Wert und dem rekursiven Aufruf ( +
für Summa und (\q qs -> qs ++ [q])
für Reverso). Die Funktionsstruktur für die obigen Beispiele lässt sich also allgemein wie folgt darstellen
-- Generic function structure
foo :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
foo op init_val [] = init_val
foo op init_val (x:xs) = x `op` foo op init_val xs
Um zu sehen, dass dieses "generische" foo funktioniert, können wir nun reverso umschreiben, indem wir foo verwenden und ihm den Operator, den Anfangswert und die Liste selbst übergeben:
-- Test: reverso using foo
foo (\curr acc -> acc ++ [curr]) [] [1,2,3,4]
Geben wir foo eine allgemeinere Typsignatur, damit es auch für andere Probleme funktioniert:
foo :: (a -> b -> b) -> b -> [a] -> b
Um nun auf Ihre Frage zurückzukommen - wir könnten den Filter so schreiben:
-- Example 3: filter
filtero :: (a -> Bool) -> [a] -> [a]
filtero p [] = []
filtero p (x:xs) = x `filterLogic` (filtero p xs)
where
filterLogic = (\curr acc -> if (p curr) then curr:acc else acc)
Auch hier ist die Struktur sehr ähnlich wie bei Summa und Reverso. Daher sollten wir in der Lage sein, foo zu verwenden, um es umzuschreiben. Nehmen wir an, wir wollen die geraden Zahlen aus der Liste [1,2,3,4] herausfiltern. Dann übergeben wir wieder foo den Operator (in diesem Fall filterLogic
), den Anfangswert und die Liste selbst. filterLogic
in diesem Beispiel nimmt eine p
Funktion, ein so genanntes Prädikat, das wir für den Aufruf definieren müssen:
let p = even in foo (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4]
foo heißt in Haskell foldr . Wir haben also den Filter mit foldr umgeschrieben.
let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4]
Also, Filter kann geschrieben werden mit foldr wie wir gesehen haben:
-- Solution 1: filter using foldr
filtero' :: (a -> Bool) -> [a] -> [a]
filtero' p xs = foldr (\curr acc -> if (p curr) then curr:acc else acc) [] xs
In Bezug auf Karte könnte man auch schreiben als
-- Example 4: map
mapo :: (a -> b) -> [a] -> [b]
mapo f [] = []
mapo f (x:xs) = x `op` (mapo f xs)
where
op = (\curr acc -> (f curr) : acc)
die daher wie folgt umgeschrieben werden kann foldr . Zum Beispiel, um jede Zahl in einer Liste mit zwei zu multiplizieren:
let f = (* 2) in foldr (\curr acc -> (f curr) : acc) [] [1,2,3,4]
Also, Karte kann geschrieben werden mit foldr wie wir gesehen haben:
-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs