如何使用runAccumT?替换StateT时execAccumT参数失效问题
AccumT 与 StateT 的核心差异及你的问题根源
你遇到的问题核心是误解了AccumT的工作逻辑:它不会自动将你的“增量”与初始累加器合并,所有对累加器的修改都需要你在操作中显式处理初始值。
先明确AccumT的基础逻辑
AccumT的类型定义为:
newtype AccumT w m a = AccumT { runAccumT :: w -> m (a, w) }
w是累加器的类型,m是底层Monad,a是操作的返回值类型runAccumT接收操作和初始累加器,返回m (a, w):前者是操作的结果,后者是最终的累加器值execAccumT是runAccumT的封装,只返回最终的累加器值(丢弃操作结果)
你的代码为什么输出7?
你写的add 7大概率是类似这样的实现:
add :: Int -> AccumT Int IO Int add x = AccumT $ \_ -> return (x, x)
这个函数直接忽略了传入的初始累加器(\_表示丢弃参数),硬编码返回结果和新累加器为x,所以execAccumT (add 7) 8自然会返回7——你的操作根本没用到初始值8。
正确实现累加逻辑
如果你想让累加器是初始值加增量,需要在操作中显式读取并修改初始值:
-- 基础实现 add :: Int -> AccumT Int IO Int add x = AccumT $ \currentW -> let newW = currentW + x in return (newW, newW)
或者用MonadAccum类提供的accum函数,更简洁地处理累加器更新:
add :: Int -> AccumT Int IO Int add x = accum (\currentW _ -> (currentW + x, currentW + x)) (return ())
如果不需要操作返回值,只更新累加器:
add :: Int -> AccumT Int IO () add x = accum (\currentW _ -> ((), currentW + x)) (return ())
此时execAccumT (add 7) 8就会返回15,符合你的预期。
额外:借助Monoid自动合并增量
如果你希望像WriterT那样自动用Monoid规则合并增量,可以将累加器类型换成Sum Int(其Monoid实例为加法),然后使用tell:
import Data.Monoid (Sum(..)) add :: Int -> AccumT (Sum Int) IO () add x = tell (Sum x)
调用时传入Sum 8作为初始值:
getSum <$> execAccumT (add 7) (Sum 8) :: IO Int
最终会返回15。
内容的提问来源于stack exchange,提问作者Ron Inbar
相关产品推荐
相关产品推荐

