Reputation: 301
Suppose I have a list fns
of partial functions from a
to b
, which I represent as functions from a
to Maybe b
, and I have an object x
of type a
. Suppose now that I want to define another function from a
to Maybe b
which takes the value of the first f x
that is not Nothing
, if such an f
exists in fns
, or the value Nothing
if no such f
exists. So basically it outputs f x
for the first f
that works, or Nothing
if no f
works.
It isn't hard to come up with some code that does the job. For example, one can create a list [f x| f <- fns]
remove all the Nothing
s from it, and then take the head of the resulting list, or Nothing
if that list is empty. But that feels clumsy, and this seems like the kind of general situation for which there is a much more stylish implementation using some built-in function in Haskell. If so, then I'd be interested to know what it is.
Upvotes: 9
Views: 1052
Reputation: 319
Warning: This is a really non-standard solution. But I personally really like it for its elegance - and the underlying mind-bending.
On https://wiki.haskell.org/Pointfree you can find a function named swing
. Its implementation and type are confusing at first:
swing :: (((a -> b) -> b) -> c -> d) -> c -> a -> d
swing = flip . (. flip id)
You can get a first idea of what it does when you see its fully applied form:
swing f c a = f ($ a) c
In conjunction with other higher-order functions it does things that almost look like magic. Examples from the link:
swing map :: [a -> b] -> a -> [b]
swing any :: [a -> Bool] -> a -> Bool
swing foldr :: b -> a -> [a -> b -> b] -> b
swing zipWith :: [a -> b -> c] -> a -> [b] -> [c]
swing find :: [a -> Bool] -> a -> Maybe (a -> Bool)
swing partition :: [a -> Bool] -> a -> ([a -> Bool], [a -> Bool])
All these functions do exactly what you would assume from the types. (But note that the wiki is a little out of date. By now, most of these functions automatically work for any Foldable
type.)
The function that you search could start from
swing mapMaybe :: [a -> Maybe b] -> a -> [b]
and then apply listToMaybe
. (Both functions are from Data.Maybe
)
A more general form would be
swing mapM :: (Monad m, Traversable t) => t (a -> m b) -> a -> m (t b)
so for example (using the ClassyPrelude
for full generality at the cost of some constraint noise, with headMay
as a more general form of listToMaybe
):
f :: (Traversable t, MonoFoldable (t b), Element (t b) ~ b)
=> t (a -> Maybe b) -> a -> Maybe b
f functions x = join $ headMay <$> swing mapM functions x
Yes, it might turn your head into mush - but it's like bending your head to see a smiley, only with your whole mind.
Upvotes: 4
Reputation: 43393
In Data.Monoid
, a newtype
copy of Maybe
, called First
, has the "take the first Just
" behaviour.
If you were looking for a function of type
[a -> First b] -> a -> First b
with the behaviour you describe, it would simply be
fold
from Data.Foldable
, because the monoid behaviour for a ->
does the pointwise lifting needed: the Monoid
for a -> First b
is exactly picking the first application outcome whic works. Sadly (for my tears over this have been many), to get Maybe
instead of First
takes a little more work.
Note that the pointwise lifting, yanking a ->
out through []
, is just the sort of job for sequenceA
, so
(asum .) . sequenceA
will do the job.
It's good to get the monoid structure you need cued from the type: in this case, accessing the Alternative
behaviour with asum
will have to do.
Upvotes: 9
Reputation: 32319
This is what Alternative
is for. It represents choice and here you are choosing between Maybe
values. You can do something like
foldr (<|>) empty [f x | f <- fns]
In Data.Foldable
you also have asum :: (Foldable t, Alternative f) => t (f a) -> f a
which does what you want even more directly:
asum [f x | f <- fns]
As a side remark, I should note that MonadPlus
also does what you want for its Maybe
instance. As in the above, you can have
foldr mplus mempty [f x | f <- fns]
And
msum [f x | f <- fns]
But IMO you should use Alternative
here since that more accurately conveys your meaning of "choice".
Upvotes: 12