Reputation: 57
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
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
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