@rzeta0 In concrete terms, you have to look at the definition of filterM
, which is apparently:
filterM p = foldr (\ x -> liftA2 (\ flg -> if flg then (x:) else id) (p x)) (pure [])
It is defined in terms of liftA2
and pure
, which for lists are defined as:
pure x = [x]
liftA2 f xs ys = [f x y | x <- xs, y <- ys]
Also, for didactic reasons, let's restore the second parameter of the function passed to liftA2
:
(\flg -> if flg then (x:) else id)
-- eta
(\flg rest -> if flg then x : rest else rest)
... and the one passed to foldr
, too:
(\ x -> liftA2 (...) (p x))
-- eta
(\x z -> liftA2 (...) (p x) z)
Putting it all together gives
pure x = [x]
liftA2 f xs ys = [f x y | x <- xs, y <- ys]
filterM p = foldr (\x z -> liftA2 (\flg rest -> if flg then x : rest else rest) (p x) z) (pure [])
and after inlining (and renaming the conflicting variables to x'
and y'
)
filterM p = foldr (\x z -> [(\flg rest -> if flg then x : rest else rest) x' y' | x' <- p x, y' <- z]) [[]]
and then
filterM p = foldr (\x z -> [(if x' then x : y' else y') | x' <- p x, y' <- z]) [[]]
So what's going on here? We're using foldr
to transform an input list into an output list (more specifically, into an output list of lists, i.e. [a] -> [[a]]
). Our "accumulator", the z
parameter of the function, is a list of possible tails generated so far (or tails that will be generated in the future, depending on how you look at it). Initially, the list of possible tails only has one element: the empty list ([[]]
).
p x
is our predicate p
applied to the current element x
. In the (\x -> [True, False])
example, x
is ignored and p x
is always [True, False]
.
Using this nested list comprehension, we iterate over all possible predicate values (i.e. [True, False]
in this case) and all possible list tails z
, and then either prepend the current element x
to the current tail y'
(x : y'
) or not (y'
), i.e. either include x
in the output list or not.