如何在 MonadPlus/Alternative 中组合然后分支

dfe*_*uer 16 haskell alternative-functor monadplus

我最近写的

do
  e <- (Left <$> m) <|> (Right <$> n)
  more actions
  case e of
    Left x -> ...
    Right y -> ...
Run Code Online (Sandbox Code Playgroud)

这看起来很尴尬。我知道protolude(和其他一些包)定义

-- Called eitherP in parser combinator libraries
eitherA :: Alternative f => f a -> f b -> f (Either a b)
Run Code Online (Sandbox Code Playgroud)

但即便如此,这一切还是感觉有点手动。有没有一些我没见过的漂亮模式可以收紧它?

DDu*_*Dub 15

我刚刚注意到OP在评论中表达了同样的想法表达了同样的想法。无论如何我都会发表我的想法。

\n
\n

Coyoneda 是一个巧妙的技巧,但对于这个特定问题来说有点过分了。我认为你所需要的只是常规的旧延续。

\n

让我们命名这些...

\n
do\n  e <- (Left <$> m) <|> (Right <$> n)\n  more actions\n  case e of\n    Left x -> fx x\n    Right y -> fy y\n
Run Code Online (Sandbox Code Playgroud)\n

然后,我们可以将其写为:

\n
do\n  e <- (fx <$> m) <|> (fy <$> n)\n  more actions\n  e\n
Run Code Online (Sandbox Code Playgroud)\n

这有点微妙 \xe2\x80\x94 在那里使用很重要,<$>即使它看起来你可能想要使用=<<,以便第一行的结果实际上是稍后执行的单子操作,而不是得到的东西立即执行。

\n

  • 不过,这两个代码片段有一个区别:在第一个代码片段中,“fx”可以清楚地提及“more actions”绑定的变量。 (3认同)
  • @DanielWagner 你绝对是对的。可以让 `fx :: TypeOfM -&gt; More -&gt; Types -&gt; m ()` 和类似的 `fy` 。然后,最后一行将是“epq”或类似的内容。如果“更多操作”中绑定了很多变量,这很快就会变得丑陋,但只有一两个变量,它就可以很好地工作。 (2认同)

dan*_*iaz 10

这个问题想太多了,但是……

在您的代码中,每个分支的类型Either可能不同,但它们不会逃脱 do 块,因为它们被LeftandRight延续“擦除”。

这看起来有点像存在主义类型。也许我们可以声明一个包含初始操作及其延续的类型,并为该类型提供一个Alternative实例。

实际上,我们不必声明它,因为 Hackage 中已经存在这样的类型:它Coyoneda来自kan-extensions

data Coyoneda f a where       
    Coyoneda :: (b -> a) -> f b -> Coyoneda f a  
Run Code Online (Sandbox Code Playgroud)

其中有有用的实例

Alternative f => Alternative (Coyoneda f)
MonadPlus f => MonadPlus (Coyoneda f)
Run Code Online (Sandbox Code Playgroud)

在我们的例子中,“返回值”本身就是一个单子动作m,所以我们想要处理类型的值Coyoneda m (m a),其中m a是整个 do 块的类型。

知道了所有这些,我们可以定义以下函数:

sandwich :: (Foldable f, MonadPlus m, Monad m) 
         => m x 
         -> f (Coyoneda m (m a)) 
         -> m a
sandwich more = join . lowerCoyoneda . hoistCoyoneda (<* more) . asum 
Run Code Online (Sandbox Code Playgroud)

重新实现原来的例子:

sandwich more [Coyoneda m xCont, Coyoneda n yCont]
Run Code Online (Sandbox Code Playgroud)

  • 我有点喜欢它!它似乎还提出了一种更基本的方法,我想我更喜欢这种方法:`do {final &lt;- (m &lt;&amp;&gt; xCont) &lt;|&gt; (n &lt;&amp;&gt; yCont); 更多的; 行动;最后}` (4认同)
  • @leftaroundabout IIRC,“Coyoneda”的另一个用途是通过“隐藏”“Coyoneda”函数组件中的 fmappings,从像“IORef”这样没有实例的类型中创建函子。https://www.reddit.com/r/haskell/comments/5v33qk/forall_a_ioref_a_lens_a_b_is_a_useful_type_does/ddyxp6x/ (2认同)

Nou*_*are 6

你也许可以这样做:

do
  let acts = do more actions
  (do x <- m; acts; ...) <|> (do y <- n; acts; ...)
Run Code Online (Sandbox Code Playgroud)

我不知道这对你来说是否更好。

more actions(当然,如果这些绑定了许多变量,那么效果就不好)

  • 我相信,这仅适用于满足左分布的实例,因为它将保留判断以查看中间操作是否失败。 (4认同)