11

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?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 8
    I'd just like to say reading this question was a rare pleasure. We get "how the heck does this code work?" questions frequently, but rarely with this level of explanation of what the asker does and does not already understand. That makes it really fun to write a good, targeted answer about exactly the gaps you have. – Daniel Wagner Apr 05 '20 at 20:58
  • Thank you Daniel! I spent a lot of time in this problem and that is why, I could pinpoint what I needed help with. –  Apr 05 '20 at 21:21
  • 1
    I would like to add that @WillNess answer works as well. It's way more easy on the eye and easy to understand. –  May 02 '20 at 23:22

2 Answers2

7

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

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    Hey Willem, what a great explanation! I have understood the full code now. Thank you sir! –  Apr 05 '20 at 20:35
1

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

Will Ness
  • 70,110
  • 9
  • 98
  • 181