So the best way to understand it is by doing it. Below there's an implementation of foldlM using foldl instead of foldr. It is a good excercise, try it and come later to the solution I'd suggest. The example explain all the reasoning I've done to achieve it, which might be different from yours, and might be bias because I've already knew about using a function accumulator.
Step 1: Let's try to write foldlM in terms of foldl
-- 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
Here you realize that f' is pure and you'd need to extract the result of f to type match. The only way to 'extract' a monadic value is with >>= operator, but such an operator needs to be wrap right after it is used.
So as a conclusion: Every time you end up with I'd like to fully unwrap this monad, just give up. Is not the right way
Step 2: Let's try to write foldlM in terms of foldl but first using [] as foldable, since it is easy to pattern match (i.e. we don't actually need to use 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, that was easy. Let compare the definition with the usual foldl definition for lists
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!! they are pretty much the same. The trivial case is about the exact same thing. The recursive case is a little bit different, you'd like to write something more like: foldlM' f (f z0 x) xs. But is doesn't compile as in step 1, so you might think OK, I don't want to apply f, just to hold such a computation and compose it with >>=. I'd like to write something more like foldlM' f (f z0 x >>=) xs if it had sense...
Step 3 Realize that what you want to accumulate is a function composition and not a result. (here I am probably bias by the fact that I already knew it because you've posted it).
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.
By the type of initFunc and using our knowledge from step 2 (the recursive definition) we can deduce that initFunc = return. The definition of f' can be completed knowing that f' should use f and >>=.
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
As you can see, it is not soooo difficult to do it. It needs practise, but I am not a professional haskell developer and I could do it myself, It is a matter of practise