How to compose functions that return Bools to one function
We can make use of any :: Foldable => (a -> Bool) -> f a -> Bool
here:
compose :: Foldable f => f (a -> Bool) -> a -> Bool
compose = flip (any . flip ($))
or as @chepner suggests, with a (&)
:
import Data.Function((&))
compose :: Foldable f => f (a -> Bool) -> a -> Bool
compose = flip (any . (&))
or without the point-free styling (and probably simpler to understand):
compose :: Foldable f => f (a -> Bool) -> a -> Bool
compose l x = any ($ x) l
The above will work with any sort of Foldable
, so a list []
, Maybe
, etc.
Look: compose xs
in your definition is a function. So you can call it with an argument - like compose xs a
, - and that will return a Bool
.
You can use this to define the recursive case.
First of all, the recursive case must return a function - because that's what your type signature states. So it must look something like:
compose (x:xs) = \a -> ...
Now, the logic would go like this: first of all, call the first function in the list - like x a
, - and if it returns true, then that's the result; otherwise, call the composition of the tail - like compose xs a
. Let's write that down:
compose (x:xs) = \a -> x a || compose xs a
Next up, you need to decide what to do with the empty list. Obviously it can be either a function that always returns True
or a function that always returns False
, there can be no other options unless you can inspect the argument somehow, which you can't, because it's of generic type.
So, should it return True
or False
? Let's see: if it returns True
, then any composition will always be True
, that's how the ||
operator works. So we might as well just write compose _ = \_ -> True
. Therefore, the only sane variant is for it to return False
.
Summing up all of the above, here's your definition:
compose [] = \a -> False
compose (x:xs) = \a -> x a || compose xs a
And of course, you can use a shorter syntax instead of returning lambdas:
compose [] a = False
compose (x:xs) a = x a || compose xs a
To implement this using monoids you can use the Any
(from Data.Monoid) boolean wrapper which implements the disjunction behaviour you want when combining values e.g.
(Any False) `mappend` (Any True)
=> Any {getAny = True}
Functions which return monoidal values are themselves monoids - mappend
ing two such functions returns a function which evalulates the argument on both functions and mappend
s the results e.g.
f :: Int -> Any
f x = Any $ x > 10
g :: Int -> Any
g x = Any $ x < 3
comp :: Int -> Any
comp = f `mappend` g
comp 0
=> Any {getAny = True}
comp 4
=> Any {getAny = False}
comp 11
=> Any {getAny = True}
So if you lift each a -> Bool
into a function a -> Any
then these be composed with mappend
.
mconcat
reduces a list of monoidal values into a single value so applying this to a list of a -> Any
function returns a function which applies the disjunction to each result. You then need to unwrap the Bool
from the resulting Any
value with getAny
.
import Data.Monoid
compose :: [(a -> Bool)] -> (a -> Bool)
compose fs x = let anyfs = map (\f -> Any . f) fs
combined = mconcat anyfs
anyResult = combined x
in getAny anyResult
This can also be written as:
compose :: [(a -> Bool)] -> (a -> Bool)
compose = (getAny .) . mconcat . (map (Any .))
As danidiaz points out in the comments, you can also use foldMap
. This also has a more general type:
compose :: Foldable t => t (a -> Bool) -> a -> Bool
compose = (getAny .) . foldMap (Any .)