Der beste Weg, es zu verstehen, ist es, es zu tun. Unten finden Sie eine Implementierung von foldlM
using foldl
anstelle von foldr
. Es ist eine gute Übung, probieren Sie es aus und kommen Sie später zu der Lösung, die ich vorschlagen würde. Das Beispiel erklärt alle Überlegungen, die ich angestellt habe, um dies zu erreichen. Diese können sich von Ihren unterscheiden und voreingenommen sein, da ich bereits über die Verwendung eines Funktionsakkumulators Bescheid wusste.
Schritt 1 : Versuchen wir, foldlM
in Bezug auf zu schreibenfoldl
-- this doesn't compile because f returning type is (m b) and not just (b)
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f z0 xs
-- So let substitute f by some undefined f'
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
where f' = undefined
-- cool, but f' should use f somehow in order to get the monadic behaviour
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
where f' b a = f somethingIDontkNow
Hier erkennen Sie, dass dies f'
rein ist und Sie das Ergebnis extrahieren müssen, um f
Übereinstimmung einzugeben. Die einzige Möglichkeit, einen monadischen Wert zu "extrahieren", ist der >>=
Operator. Ein solcher Operator muss jedoch direkt nach seiner Verwendung umbrochen werden.
Fazit: Jedes Mal, wenn Sie am Ende diese Monade vollständig auspacken möchten , geben Sie einfach auf. Ist nicht der richtige Weg
Schritt 2 : Lassen Sie uns versuchen, foldlM
in Form von zu schreiben, foldl
aber zuerst []
als faltbar zu verwenden, da es einfach ist, Muster zu finden (dh wir müssen es eigentlich nicht verwenden fold
).
-- This is not very hard. It is pretty standard recursion schema. :)
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 [] = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs
Ok, das war einfach. Vergleichen Sie die Definition mit der üblichen foldl
Definition für Listen
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 [] = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs
myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z0 [] = z0
myfoldl f z0 (x:xs) = foldl f (f z0 x) xs
Cool!! Sie sind ziemlich gleich. Der triviale Fall handelt von genau der gleichen Sache. Der rekursive Fall ist etwas anders, Sie möchten etwas mehr schreiben wie : foldlM' f (f z0 x) xs
. Wird aber nicht wie in Schritt 1 kompiliert, so dass Sie vielleicht denken, OK, ich möchte mich nicht bewerben f
, nur um eine solche Berechnung zu halten und sie zu komponieren >>=
. Ich würde gerne etwas mehr schreiben, foldlM' f (f z0 x >>=) xs
wenn es Sinn hätte ...
Schritt 3 Stellen Sie fest, dass das, was Sie akkumulieren möchten, eine Funktionszusammensetzung und kein Ergebnis ist. ( hier bin ich wahrscheinlich voreingenommen von der Tatsache, dass ich es bereits wusste, weil Sie es gepostet haben ).
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' initFunc xs
where initFunc = undefined :: b -> m b
f' = undefined :: (b -> m b) -> a -> (b -> m b) -- This type signature can be deduce because f' should be applied to initFunc and a's from t a.
Durch die Art initFunc
und Verwendung unseres Wissens aus Schritt 2 (der rekursiven Definition) können wir daraus schließen initFunc = return
. Die Definition f'
kann zu wissen , abgeschlossen werden , dass f'
verwenden soll , f
und >>=
.
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' return xs z0
-- ^^^^^^
-- |- Initial value
where f' b a = \bvalue -> b bvalue >>= \bresult -> f bresult a -- this is equivalent to (b >=> \result -> f result a) which captures the sequence behaviour of the implementation
-- ^ ^^^^^^ ^^^^^^^
-- | | |- This is the result of previous computation
-- | |- f' should return a function b -> m b. Any time you have to return a function, start writing a lambda
-- |- This b is the accumulated value and has type b -> m b
-- Following the types you can write this with enough practise
Wie Sie sehen können, ist es nicht soooo schwierig, dies zu tun. Es braucht Übung, aber ich bin kein professioneller Haskell-Entwickler und ich könnte es selbst tun. Es ist eine Frage der Übung