On an abstract level, the first non-empty value is a Monoid
called First
. It turns out, however, that if you just naively lift your IO
values into First
, you'll have a problem with action3
, since the default monoidal append operation is strict under IO
.
You can get lazy monoidal computation using the FirstIO
type from this answer. It's not going to be better than Fyodor Soikin's answer, but it highlights (I hope) how you can compose behaviour from universal abstractions.
Apart from the above-mentioned FirstIO
wrapper, you may find this function useful:
guarded :: Alternative f => (a -> Bool) -> a -> f a
guarded p x = if p x then pure x else empty
I basically just copied it from Protolude since I couldn't find one in base that has the desired functionality. You can use it to wrap your lists in Maybe
so that they'll fit with FirstIO
:
> guarded (not . null) [] :: Maybe [Int]
Nothing
> guarded (not . null) [1, 2, 3] :: Maybe [Int]
Just [1,2,3]
Do this for each action in your list of actions and wrap them in FirstIO
.
> :t (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
(firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
:: Num a => [FirstIO [a]]
In the above GHCi snippet, I'm only showing the type with :t
. I can't show the value, since FirstIO
has no Show
instance. The point, however, is that you now have a list of FirstIO
values from which mconcat
will pick the first non-empty value:
> getFirstIO $ mconcat $ (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
Just [1,2,3]
If you want to unpack the Maybe
, you can use fromMaybe
from Data.Maybe
:
answer :: IO [Integer]
answer =
fromMaybe [] <$>
(getFirstIO $ mconcat $ (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3])
This is clearly more complex than Fyodor Soikin's answer, but I'm fascinated by how Haskell enables you to assembling desired functionality by sort of 'clicking together' existing things, almost like Lego bricks.
So, to the question of does this combinator already exist? the answer is that it sort of does, but some assembly is required.