从一组 monadic 操作中获取第一个 "non-empty" 值的标准组合器

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

我确定我在这里遗漏了一些非常明显的东西。这是我试图在概念层面上实现的目标:

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]

这个假设someCombinator是否存在?我试过使用 <|>msum 玩游戏,但无法得到我想要的。

我想,这可以概括为两种方式:

-- 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

我不知道提供此功能的库,但实现起来并不难:

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

请注意,您甚至不需要 TraversableFoldable 就足够了。

在抽象层面上,第一个非空值是一个名为FirstMonoid。然而,事实证明,如果你只是天真地将你的 IO 值提升为 First,你将遇到 action3.

的问题

您可以使用 中的 FirstIO 类型进行惰性幺半群计算。它不会比 Fyodor Soikin 的回答更好,但它强调(我希望)您可以如何从通用抽象中组合行为。

除了上面提到的 FirstIO 包装器,您可能会发现这个功能很有用:

guarded :: Alternative f => (a -> Bool) -> a -> f a
guarded p x = if p x then pure x else empty

我基本上只是从 Protolude 复制它,因为我在 base 中找不到具有所需功能的。您可以使用它来将您的列表包装在 Maybe 中,以便它们适合 FirstIO:

> guarded (not . null) [] :: Maybe [Int]
Nothing
> guarded (not . null) [1, 2, 3] :: Maybe [Int]
Just [1,2,3]

对您的操作列表中的每个操作执行此操作并将它们包装在 FirstIO.

> :t (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
(firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
  :: Num a => [FirstIO [a]]

在上面的 GHCi 片段中,我只显示带有 :t 的类型。我无法显示该值,因为 FirstIO 没有 Show 实例。然而,重点是您现在有一个 FirstIO 值列表,mconcat 将从中选择第一个非空值:

> getFirstIO $ mconcat $ (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
Just [1,2,3]

如果你想解压缩 Maybe,你可以使用 fromMaybe from Data.Maybe:

answer :: IO [Integer]
answer =
  fromMaybe [] <$>
  (getFirstIO $ mconcat $ (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3])

这显然比 Fyodor Soikin 的回答更复杂,但我着迷于 Haskell 如何让您通过 'clicking together' 现有的东西组装所需的功能,几乎就像乐高积木一样。

那么,对于 这个组合器是否已经存在的问题? 答案是它有点存在,但需要一些组装。