从一组 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
请注意,您甚至不需要 Traversable
:Foldable
就足够了。
在抽象层面上,第一个非空值是一个名为First
的Monoid
。然而,事实证明,如果你只是天真地将你的 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' 现有的东西组装所需的功能,几乎就像乐高积木一样。
那么,对于 这个组合器是否已经存在的问题? 答案是它有点存在,但需要一些组装。
我确定我在这里遗漏了一些非常明显的东西。这是我试图在概念层面上实现的目标:
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
请注意,您甚至不需要 Traversable
:Foldable
就足够了。
在抽象层面上,第一个非空值是一个名为First
的Monoid
。然而,事实证明,如果你只是天真地将你的 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' 现有的东西组装所需的功能,几乎就像乐高积木一样。
那么,对于 这个组合器是否已经存在的问题? 答案是它有点存在,但需要一些组装。