0

I have a filterFirst function that only works the right way some of the time and I'm not sure why. The function should receive a Boolean argument and if it's false, it should remove the first element in the list; if not, it should leave the list alone.

Here are two conditional functions I've been using:

isNegative :: Int -> Bool
isNegative x
  | x < 0 = True
  | otherwise = False

isPositive:: Int -> Bool
isPositive x
  | x > 0 = True
  | otherwise = False

Here's the function:

filterFirst :: (a -> Bool) -> [a] -> [a]
filterFirst x xs = foldr condition (\x -> []) xs True
   where
   condition y ys True
      | x y = y : ys True
      | otherwise = ys False
   condition y ys False = y : ys False

filterFirst returns the correct answer with:

filterFirst isNegative [1,2,(-3)]
[2,-3]

But then this actually filters out the negative:

filterFirst isPositive [1,2,(-3)]
[1,2]

Why is it removing the negative number from the list and not the first element?

Also, and this might need to be a secondary post, but is there a way to change this slightly so that it filters the last element if the condition is met?

1 Answers1

0

Your function as written actually removes not the first element of the list, but the first element that doesn't match the predicate.

Equational reasoning to the rescue!

Let's unfold the foldr application:

foldr f a [x1, x2, ... xn] = f x1 (f x2 (... (f xn a)))

So applying to your example:

foldr condition (\x -> []) [1, 2, (-3)] True
    = condition 1 ( condition 2 ( condition (-3) (\x -> []) ) ) True

So you can see that foldr, as the name suggests, applies condition right-to-left. But another important thing to notice is this: the last argument True gets passed to the first application of condition, not the last one:

condition 1 (...) True

Now let's see what condition does with it. Because isPositive 1 == True, the first sub-branch of condition comes into play:

| x y = y : ys True

So the above expression becomes:

1 : (...) True

And if we now bring back the collapsed contents of parens:

1 : (condition 2 (condition (-3) (\x -> []))) True

Which is the same as:

1 : (condition 2 (condition (-3) (\x -> [])) True)

By the same logic as above, the second application of condition similarly passes the True parameter to the third application:

1 : 2 : (condition (-3) (\x -> []) True)

But the third application is different, because isPositive (-3) is not True anymore. So the second sub-branch comes into play:

| otherwise = ys False

Dropping the (-3) and passing False to the final parameter (the seed of foldr). Which makes the whole expression transform to:

1 : 2 : ((\x -> []) False)

Which is finally equivalent to:

1 : 2 : []

If there were any more negative numbers after that, they wouldn't get dropped, because the final parameter is now False, and it gets passed through all the way to the end.


Now, the root problem here is that your solution is way too complicated for the problem it's trying to solve. One of the big strengths of Haskell is that it can encode mathematical statements more or less directly, without all the silly shoehorning that we all know and love from C++ and Java.

So why not just encode your requirement directly?

-- if it's false, it should remove the first element in the list; 
filterFirst pred (x : tail) | pred x == False = tail
-- if not, it should leave the list alone.
filterFirst _ xs = xs
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • heads up this is a follow-up question to [this one](https://stackoverflow.com/q/55910708/849891). the OP wanted to do it with a fold. – Will Ness May 04 '19 at 14:53