Search code examples
haskellcompositionhigher-order-functionsfunction-compositionpointfree

Combining fragments of Haskell Code to get the bigger picture


This is the code that I came upon somewhere but want to know how this works:

    findIndices :: (a -> Bool) -> [a] -> [Int]
    findIndices _ [] = []
    findIndices pred xs = map fst (filter (pred . snd) (zip [0..] xs))

Output: findIndices (== 0) [1,2,0,3,0] == [2,4], where pred is (==0) & xs is [1,2,0,3,0]

I'll show some of my understanding:

    (zip [0..] xs)

What the above line does is put indices to everything in the list. For the input given above, it would look like this: [(0,1),(1,2),(2,0),(3,3),(4,0)].

    (pred . snd)

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.

Next, is my understanding of fst and snd. I know that

    fst(1,2) = 1 

and

    snd(1,2) = 2

How do these two commands make sense in the code?

My understanding of filter is that it returns a list of items that match a condition. For instance,

    listBiggerThen5 = filter (>5) [1,2,3,4,5,6,7,8,9,10]

would give [6,7,8,9,10]

My understanding of map is that it applies a function to every item on the list. For instance,

    times4 :: Int -> Int
    times4 x = x * 4
    listTimes4 = map times4 [1,2,3,4,5]

would give [4,8,12,16,20]

How does this work overall? I think I have been comprehensive in what I know so far but can't quite put the pieces together. Can anybody help me out?


Solution

  • 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 [].