如何合并然后在 MonadPlus/Alternative 中分支
How to combine and then branch in MonadPlus/Alternative
我最近写了
do
e <- (Left <$> m) <|> (Right <$> n)
more actions
case e of
Left x -> ...
Right y -> ...
这看起来很尴尬。我知道 protolude
(和其他一些包)定义
-- Called eitherP in parser combinator libraries
eitherA :: Alternative f => f a -> f b -> f (Either a b)
但即便如此,感觉还是有点手动。有没有我没见过的用于收紧它的漂亮图案?
你也许可以这样做:
do
let acts = do more actions
(do x <- m; acts; ...) <|> (do y <- n; acts; ...)
不知道你觉得这样好不好。
(当然,如果那些 more actions
绑定了很多变量,效果就不太好)
这是方式想多了这个问题,但是...
在您的代码中,Either
的每个分支的类型可能不同,但它们不会转义 do-block,因为它们被 Left
和 Right
延续。
这看起来有点像存在主义类型。或许我们可以声明一个类型,将初始操作和它的延续打包在一起,并为该类型提供一个 Alternative
实例。
其实我们不用声明,因为Hackage中已经存在这样的类型:Coyoneda
from kan-extensions.
data Coyoneda f a where
Coyoneda :: (b -> a) -> f b -> Coyoneda f a
哪些实例有用
Alternative f => Alternative (Coyoneda f)
MonadPlus f => MonadPlus (Coyoneda f)
在我们的例子中,“return 值”本身就是一个单子动作 m
,因此我们要处理 Coyoneda m (m a)
类型的值,其中 m a
是总体类型 do-block.
了解所有这些,我们可以定义以下函数:
sandwich :: (Foldable f, MonadPlus m, Monad m)
=> m x
-> f (Coyoneda m (m a))
-> m a
sandwich more = join . lowerCoyoneda . hoistCoyoneda (<* more) . asum
重新实现原始示例:
sandwich more [Coyoneda m xCont, Coyoneda n yCont]
我刚刚注意到 OP 表达了同样的想法 。无论如何我都会post我的想法。
Coyoneda 是一个巧妙的技巧,但对于这个特定问题来说有点矫枉过正。我想你所需要的只是常规的旧延续。
让我们将这些 ...
命名为:
do
e <- (Left <$> m) <|> (Right <$> n)
more actions
case e of
Left x -> fx x
Right y -> fy y
那么,我们可以这样写:
do
e <- (fx <$> m) <|> (fy <$> n)
more actions
e
这有点微妙——在这里使用 <$>
很重要,即使看起来您可能想使用 =<<
这样第一行的结果实际上是一个 monadic 动作稍后执行而不是立即执行。
我最近写了
do
e <- (Left <$> m) <|> (Right <$> n)
more actions
case e of
Left x -> ...
Right y -> ...
这看起来很尴尬。我知道 protolude
(和其他一些包)定义
-- Called eitherP in parser combinator libraries
eitherA :: Alternative f => f a -> f b -> f (Either a b)
但即便如此,感觉还是有点手动。有没有我没见过的用于收紧它的漂亮图案?
你也许可以这样做:
do
let acts = do more actions
(do x <- m; acts; ...) <|> (do y <- n; acts; ...)
不知道你觉得这样好不好。
(当然,如果那些 more actions
绑定了很多变量,效果就不太好)
这是方式想多了这个问题,但是...
在您的代码中,Either
的每个分支的类型可能不同,但它们不会转义 do-block,因为它们被 Left
和 Right
延续。
这看起来有点像存在主义类型。或许我们可以声明一个类型,将初始操作和它的延续打包在一起,并为该类型提供一个 Alternative
实例。
其实我们不用声明,因为Hackage中已经存在这样的类型:Coyoneda
from kan-extensions.
data Coyoneda f a where
Coyoneda :: (b -> a) -> f b -> Coyoneda f a
哪些实例有用
Alternative f => Alternative (Coyoneda f)
MonadPlus f => MonadPlus (Coyoneda f)
在我们的例子中,“return 值”本身就是一个单子动作 m
,因此我们要处理 Coyoneda m (m a)
类型的值,其中 m a
是总体类型 do-block.
了解所有这些,我们可以定义以下函数:
sandwich :: (Foldable f, MonadPlus m, Monad m)
=> m x
-> f (Coyoneda m (m a))
-> m a
sandwich more = join . lowerCoyoneda . hoistCoyoneda (<* more) . asum
重新实现原始示例:
sandwich more [Coyoneda m xCont, Coyoneda n yCont]
我刚刚注意到 OP 表达了同样的想法
Coyoneda 是一个巧妙的技巧,但对于这个特定问题来说有点矫枉过正。我想你所需要的只是常规的旧延续。
让我们将这些 ...
命名为:
do
e <- (Left <$> m) <|> (Right <$> n)
more actions
case e of
Left x -> fx x
Right y -> fy y
那么,我们可以这样写:
do
e <- (fx <$> m) <|> (fy <$> n)
more actions
e
这有点微妙——在这里使用 <$>
很重要,即使看起来您可能想使用 =<<
这样第一行的结果实际上是一个 monadic 动作稍后执行而不是立即执行。