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

为什么Haskell中不存在MonadMaybe类型类?

关于MonadMaybe类型类的疑问与探索

我对MonadState的理解是:任何实现这个类型类的类型(或者类型组合,比如ReaderT r m a)都必须支持get+put(或者仅state)操作,才能表现得像State单子——它本质是个Monad,但又多了这些专属操作,get和put就是定义它特性的核心API。

那如果存在MonadMaybe类型类,它会是什么样?如果一个单子栈想表现得像Maybe单子,是不是就得实现这个类型类?这到底意味着什么?毕竟Maybe本身就是个Monad,但它好像没有能定义自身特性的专属API,不是吗?

我看过Reddit上Edward Kmett的回答,但没太搞懂,可能是当局者迷。

这个类型类的定义会是怎样的?

class Monad m => MonadMaybe m where
...???

具体代码示例分析

先看一段硬编码了单子栈顺序的代码:

foo :: Monad m => MaybeT (StateT Int (ReaderT Int m)) Int
foo = do
  s <- get
  r <- ask
  if s + r == 0
    then mzero -- 见底部注释 (¹)
    else return $ s + r

这个栈的顺序是MaybeT→StateT→ReaderT,所以只能按这个顺序剥离运行:

bar :: IO (Maybe Int)
bar = runMaybeT foo `evalStateT` 3 `runReaderT` 3

而不能写成下面这样:

bar :: IO (Maybe Int)
bar = runMaybeT (foo `evalStateT` 3 `runReaderT` 3)

因为单子层必须从最外层开始剥离。

要让bar摆脱这种顺序限制,我们可以让m实现对应的类型类,改写foo的签名:

foo :: (MonadState Int m, MonadReader Int m) => MaybeT m Int
foo = do
  s <- get
  r <- ask
  if s + r == 0
    then mzero
    else return $ s + r

现在两种bar的写法都能正常运行了。

这时候我就在想,如果有MonadMaybe类型类,我就能把foo的签名改成这样:

foo :: (MonadMaybe m, MonadState Int m, MonadReader Int m) => m Int

这样一来,bar就可以用任意顺序剥离单子层:

bar :: IO (Maybe Int)
bar = (runMaybeT foo) `evalStateT` 3 `runReaderT` 3 -- MSR
bar = (runMaybeT foo) `runReaderT` 3 `evalStateT` 3 -- MRS
bar = runMaybeT (foo `runReaderT` 3) `evalStateT` 3 -- RMS
bar = runMaybeT (foo `evalStateT` 3) `runReaderT` 3 -- SMR
bar = runMaybeT (foo `runReaderT` 3 `evalStateT` 3) -- RSM
bar = runMaybeT (foo `evalStateT` 3 `runReaderT` 3) -- SRM

上面的注释用MSR这类缩写表示三个单子层的处理顺序。


(¹) 如果把runMaybeT改成(runMaybeT . forever),就能简洁表示一个可以永久运行、也可以通过mzero提前退出的计算。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:18:14