La meilleure façon de le comprendre est donc de le faire. Ci-dessous, il y a une implémentation de l' foldlM
utilisation foldl
au lieu de foldr
. C'est un bon exercice, essayez-le et revenez plus tard à la solution que je suggérerais. L'exemple explique tout le raisonnement que j'ai fait pour y parvenir, qui peut être différent du vôtre et peut être biaisé parce que je savais déjà comment utiliser un accumulateur de fonctions.
Étape 1 : Essayons d'écrire foldlM
en termes defoldl
-- 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
Ici, vous réalisez que f'
c'est pur et vous devez extraire le résultat de f
pour taper match. La seule façon d'extraire une valeur monadique est avec l' >>=
opérateur, mais un tel opérateur doit être bouclé juste après son utilisation.
Donc, en conclusion: chaque fois que vous vous retrouvez avec, j'aimerais déballer complètement cette monade , abandonnez. N'est-ce pas la bonne façon
Étape 2 : Essayons d'écrire foldlM
en termes de foldl
mais en utilisant d'abord []
comme pliable, car il est facile de faire correspondre les motifs (c'est-à-dire que nous n'avons pas vraiment besoin d'utiliser 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, c'était facile. Comparons la définition avec la foldl
définition habituelle des listes
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!! ils sont à peu près les mêmes. Le cas trivial concerne exactement la même chose. Le cas récursif est un autre petit peu, vous voulez écrire quelque chose comme: foldlM' f (f z0 x) xs
. Mais ce n'est pas compiler comme à l'étape 1, donc vous pourriez penser OK, je ne veux pas appliquer f
, juste pour tenir un tel calcul et le composer avec >>=
. J'aimerais écrire quelque chose de plus comme foldlM' f (f z0 x >>=) xs
si ça avait du sens ...
Étape 3 Réalisez que ce que vous voulez accumuler est une composition de fonction et non un résultat. ( ici, je suis probablement biaisé par le fait que je le savais déjà parce que vous l'avez posté ).
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.
Par le type initFunc
et l'utilisation de nos connaissances de l'étape 2 (la définition récursive), nous pouvons en déduire initFunc = return
. La définition de f'
peut être complétée en sachant que f'
devrait utiliser f
et >>=
.
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
Comme vous pouvez le voir, ce n'est pas si difficile à faire. Il a besoin de pratique, mais je ne suis pas un développeur de haskell professionnel et je pourrais le faire moi-même, c'est une question de pratique