Combining fragments of Haskell Code to get the bigger picture

In Haskell we like to say, follow the types. Indeed the pieces connect as if by wires going from type to corresponding type:

( first, function composition is:

   (f >>> g) x  =  (g . f) x  =        g (f x)
   (f >>> g)    =  (g . f)    =  \x -> g (f x)

and function composition type inference rule is:

    f        :: a -> b                   --      x  :: a
          g  ::      b -> c              --    f x  :: b
   -------------------------             -- g (f x) :: c
    f >>> g  :: a ->      c
    g  .  f  :: a ->      c

Now, )

findIndices :: (b -> Bool) -> [b] -> [Int]
findIndices pred  = \xs -> map fst ( filter (pred . snd) ( zip [0..] xs ))
                  =        map fst . filter (pred . snd) . zip [0..]
                  =  zip [0..]  >>>  filter (snd >>> pred)  >>>  map fst
---------------------------------------------------------------------------
zip :: [a] ->          [b]        ->        [(a,  b)]
zip  [0..] ::          [b]        ->        [(Int,b)]
---------------------------------------------------------------------------
        snd           :: (a,b) -> b
                pred  ::          b -> Bool
       ------------------------------------
       (snd >>> pred) :: (a,b)      -> Bool
---------------------------------------------------------------------------
filter ::               (t          -> Bool) -> [t]   -> [t]
filter (snd >>> pred) ::                      [(a,b)] -> [(a,b)]
filter (snd >>> pred) ::                    [(Int,b)] -> [(Int,b)]
---------------------------------------------------------------------------
    fst ::                                   (a,   b) -> a
map     ::                                  (t        -> s) -> [t] -> [s]
map fst ::                                                 [(a,b)] -> [a]
map fst ::                                               [(Int,b)] -> [Int]

so, overall,

zip  [0..] ::          [b]        ->        [(Int,b)]
filter (snd >>> pred) ::                    [(Int,b)] -> [(Int,b)]
map fst ::                                               [(Int,b)] -> [Int]
---------------------------------------------------------------------------
findIndices pred ::    [b] ->                                         [Int]

You've asked, how do these pieces fit together?

This is how.


With list comprehensions, your function is written as

findIndices pred xs = [ i | (i,x) <- zip [0..] xs, pred x ]

which in pseudocode reads:

"result list contains i for each (i,x) in zip [0..] xs such that pred x holds".

It does this by turning the n-long

xs = [a,b,...,z] = [a] ++ [b] ++ ... ++ [z]

into

  [0 | pred a] ++ [1 | pred b] ++ ... ++ [n-1 | pred z]

where [a | True] is [a] and [a | False] is [].


I found that this means something like pred (snd (x)). My question is, is x the list made from the zip line? I'm leaning towards yes but my guess is flimsy.

Well pred . snd, means \x -> pred (snd x). So this basically constructs a function that maps an element x on pred (snd x).

This thus means that the expression looks like:

filter (\x -> pred (snd x)) (zip [0..] xs)

Here x is thus a 2-tuple generated by zip. So in order to know if (0, 1), (1,2), (2, 0), etc. are retained in the result, snd x will take the second element of these 2-tuples (so 1, 2, 0, etc.), and check if the pred on tha element is satisfied or not. If it is satisfied, it will retain the element, otherwise that element (the 2-tuple) is filtered out.

So if (== 0) is the predicate, then filter (pred . snd) (zip [0..] xs) will contain the 2-tuples [(2, 0), (4, 0)].

But now the result is a list of 2-tuples. If we want the indices, we somehow need to get rid of the 2-tuple, and the second element of these 2-tuples. We use fst :: (a, b) -> a for that: this maps a 2-tuple on its first element. So for a list [(2, 0), (4, 0)], map fst [(2, 0), (4, 0)] will return [2, 4].