为何StateT的evalStateT/execStateT采用Monad约束而非更宽松的Functor?
关于StateT中evalStateT/execStateT约束的疑问
我查看StateT monad transformer的源码时,发现evalStateT和execStateT函数的定义带有Monad m约束:
evalStateT :: Monad m => StateT s m a -> s -> m a evalStateT m s = do ~(a, _) <- runStateT m s return a execStateT :: Monad m => StateT s m a -> s -> m s execStateT m s = do ~(_, s') <- runStateT m s return s'
但我发现采用Functor m约束的实现似乎同样可行:
evalStateT :: Functor m => StateT s m a -> s -> m a evalStateT = (fmap fst .) . runStateT execStateT :: Functor m => StateT s m a -> s -> m s execStateT = (fmap snd .) . runStateT
请问为何要使用更严格的约束?
原因分析
- 历史兼容性:在GHC 7.10之前,
Functor并不是Monad的默认超类,当时很多库函数为了兼容旧代码,会选择使用Monad约束而非更宽松的Functor。transformers作为Haskell生态中历史悠久的基础库,延续了早期的定义。 - API设计一致性:StateT相关的很多核心操作(如
modify、get)本身需要Monad约束,为了让整个API的约束风格统一,减少用户需要区分不同函数约束的成本,库维护者选择给evalStateT和execStateT也加上Monad约束。 - 早期实现的惯性:原代码使用惰性模式匹配配合do语法实现,在Haskell还未完善
Applicative和Functor层级的时期,用Monad的do语法写这类逻辑更直观,开发者当时可能没有特意去优化约束的严格性——毕竟Monad包含Functor,现有代码不会因为约束更严格而无法运行。 - 保守的API稳定性:虽然把约束从
Monad改为Functor理论上是向下兼容的(Monad是Functor的子类),但基础库的维护者通常倾向于保持API的稳定性,避免不必要的变更,防止影响大量依赖该库的上层代码。
内容的提问来源于stack exchange,提问作者rexcfnghk
相关产品推荐
相关产品推荐

