user53067
user53067

Reputation:

How do you combine filter conditions

The filter class of functions takes a condition (a -> Bool) and applies it when filtering.

What is the best way to use a filter on when you have multiple conditions?

Used the applicative function liftA2 instead of liftM2 because I for some reason didn't understand how liftM2 worked within pure code.

Upvotes: 29

Views: 18211

Answers (5)

markasoftware
markasoftware

Reputation: 12652

Yet another one, using function functors, which easily extends to a greater number of conditions:

filter (and <$> sequence [odd, (> 100)]) [1..200]

Upvotes: 1

Quint
Quint

Reputation: 1214

If you have a list of filtering functions of type a -> Bool and want to combine them into one concise filtering function of the same type, we can write functions to do just. Which of the two functions below that you use will depend on the filter behavior you need.

anyfilt :: [(a -> Bool)] -> (a -> Bool)
anyfilt fns = \el -> any (\fn -> fn el) fns

allfilt :: [(a -> Bool)] -> (a -> Bool)
allfilt fns = \el -> all (\fn -> fn el) fns

anyfilt will return true if any of the filter functions return true and false if all of the filter functions return false. allfilt will return true if all of the filter functions return true and false if any of the filter functions return false. Note that you cannot η-reduce either function as the references to fns on the RHS are within anonymous functions.

Use it like this:

filterLines :: [String] -> [String]
filterLines = let
  isComment = isPrefixOf "# "
  isBlank = (==) ""
  badLine = anyfilt([isComment, isBlank])
  in filter (not . badLine)

main = mapM_ putStrLn $ filterLines ["# comment", "", "true line"]
--> "true line"

Upvotes: 4

Ayman Hourieh
Ayman Hourieh

Reputation: 137146

Let's say your conditions are stored in a list called conditions. This list has the type [a -> Bool].

To apply all conditions to a value x, you can use map:

map ($ x) conditions

This applies each condition to x and returns a list of Bool. To reduce this list into a single boolean, True if all elements are True, and False otherwise, you can use the and function:

and $ map ($ x) conditions

Now you have a function that combines all conditions. Let's give it a name:

combined_condition x = and $ map ($ x) conditions

This function has the type a -> Bool, so we can use it in a call to filter:

filter combined_condition [1..10]

Upvotes: 11

bdonlan
bdonlan

Reputation: 231103

The liftM2 combinator can be used in the Reader monad to do this in a 'more functional' way:

import Control.Monad
import Control.Monad.Reader

-- ....

filter (liftM2 (&&) odd (> 100)) [1..200]

Note that the imports are important; Control.Monad.Reader provides the Monad (e ->) instance that makes this all work.

The reason this works is the reader monad is just (e ->) for some environment e. Thus, a boolean predicate is a 0-ary monadic function returning bool in an environment corresponding to its argument. We can then use liftM2 to distribute the environment over two such predicates.

Or, in simpler terms, liftM2 will act a bit like this when the types work out:

liftM2 f g h a = f (g a) (h a)

You can also define a new combinator if you want to be able to chain these easily, and/or don't want to mess with liftM2:

(.&&.) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
(.&&.) f g a = (f a) && (g a)
-- or, in points-free style:
(.&&.) = liftM2 (&&)    

filter (odd .&&. (> 5) .&&. (< 20)) [1..100]

Upvotes: 37

Jonas
Jonas

Reputation: 19642

Well, you can combine functions however you want in Haskell (as long as the types are correct) and using lambdas you don't even have to name your predicate function, i.e.,

filter (\x -> odd x && x > 100) [1..200]

Upvotes: 22

Related Questions