1

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

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
4

2 回答 2

3

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

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

请注意,您甚至不需要Traversable:Foldable就足够了。

于 2020-01-18T07:32:06.000 回答
2

在抽象层面上,第一个非空值是被Monoid调用的First。然而,事实证明,如果你只是天真地将你的IO值提升到First,你会遇到 的问题action3因为默认的 monoidal 附加操作是严格的IO

您可以使用此答案FirstIO中的类型获得惰性单曲面计算。它不会比 Fyodor Soikin 的回答更好,但它强调(我希望)你可以如何从通用抽象中组合行为。

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

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

我基本上只是从Protolude复制了它,因为我在基础中找不到具有所需功能的。您可以使用它来包装您的列表,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,可以使用fromMaybefrom Data.Maybe

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

这显然比 Fyodor Soikin 的回答更复杂,但我对 Haskell 如何让您通过“点击”现有事物(几乎就像乐高积木)来组装所需功能感到着迷。

那么,对于这个组合器是否已经存在的问题?答案是确实如此,但需要进行一些组装。

于 2020-01-18T12:48:11.893 回答