You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在基于Alternative的asumThese函数中移除Monad约束?

Haskell函数asumThese的Monad约束能否移除?

我使用these库编写了如下函数:

import Control.Applicative (Alternative ((<|>)), optional)
import Data.These (These(..))

asumThese :: (Alternative f, Monad f) => f a -> f b -> f (These a b)
asumThese x y = optional x >>= \case
  Just x' -> maybe (This x') (These x') <$> optional y
  Nothing -> That <$> y

请问是否可以移除Monad约束,或者必须使用bind?我直觉认为无法避免Monad约束,但希望有人能解释原因,帮助我日后判断哪些函数需要Monad,哪些可泛化为Applicative或Alternative。我感觉这与Monad的顺序执行特性有关,但无法明确区分,因为f <$> x <*> y和f <$> y <*> x也存在顺序差异,Applicative同样有顺序性。


结论与解释

这个函数确实无法移除Monad约束,核心原因在于你需要根据optional x的执行结果动态决定后续操作——这种“用前一步结果选择下一步做什么”的逻辑,正是Monad独有的核心能力,Applicative做不到这一点。

你提到Applicative也有顺序性,这点没错,但Applicative的顺序是静态、提前固定的:不管x执行出什么结果,f <$> x <*> y里y都会被执行,只是把两者的结果结合;反过来f <$> y <*> x也是先执行y再执行x,顺序不会因为前一步的结果改变。

但你的函数逻辑是动态分支的:

  • 当optional x返回Just x'时,执行optional y并根据其结果生成This或These
  • 当optional x返回Nothing时,直接执行y生成That

这里后续要执行的操作(是调用optional y还是y)完全由x的执行结果决定,这种动态选择只能通过Monad的>>=(bind)实现。Applicative没有提供这种分支能力,它只能描述固定顺序的组合,无法根据前一步结果调整后续计算。

再举个实际的例子:如果尝试用纯Applicative重写,你可能会写出这样的代码:

asumThese :: Alternative f => f a -> f b -> f (These a b)
asumThese x y = This <$> x <* empty
             <|> These <$> x <*> y
             <|> That <$> y

但这里有个致命问题:如果x是带副作用的操作(比如IO),第一个分支会执行一次x,第二个分支又会执行一次x——而原函数里x只执行一次,复用其结果做分支判断。这就是Monad和Applicative的关键区别:Monad可以保证计算只执行一次,然后复用结果;Applicative的组合是独立的,每个分支里的x都会重新执行。

如何判断是否需要Monad

可以通过两个核心点来判断:

  • 如果逻辑需要根据前一步计算的结果动态选择后续计算流程,必须用Monad
  • 如果逻辑需要复用前一步计算的结果,避免重复执行,必须用Monad
  • 只要逻辑是固定顺序的组合,不需要根据前一步结果做分支,即使有顺序性,也可以用Applicative

内容的提问来源于stack exchange,提问作者Clinton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:22:44