xentaquadrin
xentaquadrin

Reputation: 57

Haskell list filtering with two arguments

I have to write a function, which filters with one argument that results True, then filters with another argument which results False

I tried this:

selectUnless :: (t -> Bool) -> (t -> Bool) -> [t] -> [t]
selectUnless fx gx (x:xs) = filter gx (filter fx (x:xs))

But I need that list where "not gx".

For example:

selectUnless (>= 2) (==2) [1,2,3,4] == [3,4]
selectUnless even odd [1..50] == [2,4..50]

Upvotes: 1

Views: 566

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477854

Haskell has a not :: Bool -> Bool operator that transforms True into False and vice versa.

The problem is of course that gx is not a Bool, but a function t -> Bool. We can however construct a function like:

\x -> not (gx x)

This will thus apply not to the result of gx x. Or we can use (.) :: (b -> c) -> (a -> b) -> a -> c, like:

selectUnless :: (t -> Bool) -> (t -> Bool) -> [t] -> [t]
selectUnless fx gx = filter (not . gx) . filter fx

We can even make thus completely point-free by using liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c:

import Control.Applicative(liftA2)

selectUnless :: (t -> Bool) -> (t -> Bool) -> [t] -> [t]
selectUnless = (filter .) . (. (not .)) . liftA2 (&&)

or a more elegant solution provided by @JonPurdy:

import Control.Applicative(liftA2)
import Data.Function(on)

pre :: (a -> b) -> (b -> c) -> a -> c
pre = flip (.)

filtered :: (([a] -> [a]) -> ([a] -> [a]) -> c) -> (a -> Bool) -> (a -> Bool) -> c
filtered = (`on` filter)

selectUnless :: (a -> Bool) -> (a -> Bool) -> [a] -> [a]
selectUnless = pre (not .) . filtered (.)

Note that your original code could not process an empty list: indeed you only specified the (x:xs) pattern in your function for the list, not []. It is not necessary however to use extra patterns here, filter already can handle both empty and non-empty lists.

Upvotes: 4

Rein Henrichs
Rein Henrichs

Reputation: 15605

Because filter f . filter g = filter (\x -> f x && g x), we only need some way to invert g. This exists as not, as Willem mentions. So we have:

selectUnless f g = filter (\x -> f x && not (g x))

If you want to be a bit more clever, you can lift &&:

(<&&>) = liftA2 (&&)
infixr 3 <&&>

selectUnless f g = filter (f <&&> not . g) 

You might even decide that this is concise and intention-revealing enough to not need its own name.

Upvotes: 4

Related Questions