Standard combinator to get first “non-empty” value from a set of monadic actions

ⅰ亾dé卋堺 提交于 2020-02-05 02:34:07

问题


I'm sure I am missing something very obvious here. Here's what I'm trying to achieve at a conceptual level:

action1 :: (MonadIO m) => m [a]
action1 = pure []

action2 :: (MonadIO m) => m [a]
action2 = pure [1, 2, 3]

action3 :: (MonadIO m) => m [a]
action3 = error "should not get evaluated"

someCombinator [action1, action2, action3] == m [1, 2, 3]

Does this hypothetical someCombinator exist? I have tried playing round with <|> and msum but couldn't get what I want.

I guess, this could be generalised in two ways:

-- Will return the first monadic value that is NOT an mempty 
-- (should NOT blindly execute all monadic actions)
-- This is something like the msum function

someCombinator :: (Monoid a, Monad m, Traversable t, Eq a) => t m a -> m a

-- OR

-- this is something like the <|> operator

someCombinator :: (Monad m, Alternative f) => m f a -> m f a -> m f a

回答1:


I'm not aware of a library that provides this, but it's not hard to implement:

someCombinator :: (Monoid a, Monad m, Foldable t, Eq a) => t (m a) -> m a 
someCombinator = foldr f (pure mempty)
    where
        f item next = do
            a <- item
            if a == mempty then next else pure a

Note that you don't even need Traversable: Foldable is enough.




回答2:


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.



来源:https://stackoverflow.com/questions/59798367/standard-combinator-to-get-first-non-empty-value-from-a-set-of-monadic-actions

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!