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

如何实现不短路、延迟到值使用时收集错误的Haskell Monad实例?

实现非短路错误收集的解决方案

要实现你需要的非短路错误收集(仅在值被实际使用时触发短路并返回所有累积错误),核心问题在于Monad的>>=语义天然依赖前序计算的结果,无法直接做到非短路的错误累积。但我们可以通过GHC的ApplicativeDo语言扩展,让do语句编译为Applicative风格的操作,从而复用你已实现的Errors类型Applicative实例,完美满足需求。

代码实现

1. 定义Errors类型及基础实例

{-# LANGUAGE ApplicativeDo #-}  -- 启用ApplicativeDo扩展

newtype Errors e a = Errors { runErrors :: ([e], Maybe a) }
  deriving (Show, Eq)

-- Functor实例:仅映射值,保留错误列表
instance Functor (Errors e) where
  fmap f (Errors errs val) = Errors errs (fmap f val)

-- Applicative实例:累积双方错误,仅当双方都有值时才计算结果
instance Semigroup e => Applicative (Errors e) where
  pure x = Errors [] (Just x)
  Errors e1 f <*> Errors e2 x = Errors (e1 <> e2) (f <*> x)

2. 四种期望行为场景示例

-- 场景1:所有步骤成功,返回空错误列表和成功值
allSuccess :: Errors String Int
allSuccess = do
  a <- pure 10
  b <- pure 20
  pure (a + b)
-- runErrors allSuccess → ([], Just 30)

-- 场景2:某步骤出错,但未使用错误值,返回累积错误和成功值
unusedError :: Errors String Int
unusedError = do
  _ <- Errors ["参数格式错误"] Nothing  -- 错误值未被使用
  b <- pure 50
  pure b
-- runErrors unusedError → (["参数格式错误"], Just 50)

-- 场景3:某步骤出错且使用错误值,返回累积错误和失败结果
usedError :: Errors String Int
usedError = do
  a <- Errors ["数值超出范围"] Nothing  -- 错误值被后续计算使用
  b <- pure 30
  pure (a + b)
-- runErrors usedError → (["数值超出范围"], Nothing)

-- 场景4:多个步骤出错,返回所有累积错误和失败结果
multipleErrors :: Errors String Int
multipleErrors = do
  a <- Errors ["用户名不能为空"] Nothing
  b <- Errors ["密码长度不足"] Nothing
  pure (a + b)
-- runErrors multipleErrors → (["用户名不能为空", "密码长度不足"], Nothing)

为什么选择ApplicativeDo?

  • 你的Errors类型的Applicative实例已经实现了错误累积的核心逻辑:<*>会同时计算左右两边的结果,收集所有错误,仅当两边都有有效时值才生成结果。
  • ApplicativeDo扩展允许你用熟悉的do语法编写Applicative风格的代码,无需实现Monad实例(而Monad的>>=语义无法做到非短路的错误累积,强行实现会违反Monad法则或引入运行时风险)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:05:57