peroni_santo
peroni_santo

Reputation: 367

boolean operators over multiple elements

I know one can do:

any (>3) [1,2,3,4,5]

but what is the elegant way of implementing:

any and[(>3),(<5)] [1,2,3,4,5]

or

all or[(<2),(>4)] [1,2,3,4,5]

etc?

Upvotes: 4

Views: 1179

Answers (4)

Daniel
Daniel

Reputation: 27549

Another approach is to use Monoids. Bool is a Monoid when wrapped in All or Any from Data.Monoid. We need that because there are two ways to combine [Bool] - we can either use && or ||. That's why there are the types All :: Bool -> All and Any :: Bool -> Any which are instances of Monoid. For example:

> import Data.Monoid
> getAll $ mconcat [All True, All True, All False]
False
> getAll $ mconcat [All True, All True, All True]
True
> getAny $ mconcat [Any True, Any True, Any False]
True

The other fact that we use is the Monoid instance for functions (again from Data.Monoid):

instance Monoid b => Monoid (a -> b) where
        mempty _ = mempty
        mappend f g x = f x `mappend` g x

Now we can append functions:

> :t All
All :: Bool -> All
> :t (<5)
(<5) :: (Num a, Ord a) => a -> Bool
> :t All . (<5)
All . (<5) :: (Num a, Ord a) => a -> All
> :t ((All . (<5)) <> (All . (>3)))
((All . (<5)) <> (All . (>3))) :: (Num a, Ord a) => a -> All
> getAll $ ((All . (<5)) <> (All . (>3))) 4
True

Generalizing this to lists of functions:

> getAll $ mconcat [(All. (<5)), (All . (>3))] $ 4
True
> getAll $ mconcat (map (All .) [(<5), (>3)]) $ 4
True

Then searching http://www.haskell.org/hoogle/ for (a->b) -> [a] -> b we see foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m which we can use instead of mconcat . map:

> import Data.Foldable
> getAll $ foldMap (All .) [(<5), (>3)] $ 4
True

And finally mapping it over a list of numbers:

> map (getAll . foldMap (All .) [(<5), (>3)]) $ [1..5]
[False,False,False,True,False]
> Prelude.or $ map (getAll . foldMap (All .) [(<5), (>3)]) $ [1..5]
True

Upvotes: 3

luqui
luqui

Reputation: 60463

Your notation and[(>3),(<5)] can be almost directly implemented as a higher order function. I'll call it andP, since any takes a predicate and a list of values, and we want a function that takes a list of predicates.

andP :: [a -> Bool] -> a -> Bool
andP ps x = all ($ x) ps

Now

andP [(>3), (<5)] x = x > 3 && x < 5

and you can write as in your initial request as

any (andP [(>3), (<5)]) [1,2,3,4,5]

As a side note, for this particular example, I think a clearer way would be:

between :: (Ord a) => a -> a -> a -> Bool
between lo hi x = lo < x && x < hi

any (between 3 5) [1,2,3,4,5]

Upvotes: 6

Jan Christiansen
Jan Christiansen

Reputation: 3173

You can also define an operator that takes a list of predicates by employing some Monoid instances as follows.

test = any (andP [(>3),(<5)]) [1,2,3,4,5]

andP :: [a -> Bool] -> a -> Bool
andP ps = getAll . mconcat (map (All.) ps)

Upvotes: 2

AndrewC
AndrewC

Reputation: 32455

I believe you'd like to check whether there are any elements that are both (<5) and (>3).

You can do that this way:

any (\x -> x > 3 && x < 5) [1..5]

and your the other one can be done by

any (\x -> x < 2 || x > 4) [1..5]

But maybe it would be more fun to define && and || to work on functions:

infixr 3 &&&
infixr 3 |||

(&&&) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
(f &&& g) x = f x && g x

(|||) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
(f ||| g) x = f x || g x

so now we can rewrite your examples as:

any ((>3) &&& (<5)) [1..5]
any ((<2) ||| (>4)) [1..5]

Upvotes: 11

Related Questions