为什么变压器是“运行”功能的第一个参数?

时间:2014-03-27 06:53:39

标签: haskell monad-transformers

我的意思是为什么不到最后?

由于这个评估变换器堆栈的惯例,人们必须编写一个类似的尴尬之处:

runStateT (runReaderT (runRWST stack r s) r') s'

而不是:

runStateT s' $ runReaderT r' $ runRWST r s $ stack

将它与立即do结合起来变得更加尴尬:

let 
  action = do
    liftIO $ putStrLn "Do"
    liftIO $ putStrLn "something"
  in runStateT (runReaderT (runRWST action r s) r') s'

而不是:

runStateT s' $ runReaderT r' $ runRWST r s $ do
  liftIO $ putStrLn "Do"
  liftIO $ putStrLn "something"

这是否有任何动机,或者这只是一个不幸的约定?

BTW,我确实意识到当前的约定使得使用记录语法实现“运行”功能变得容易,但这不能成为一个论据,因为库必须更易于实现的易用性。

3 个答案:

答案 0 :(得分:4)

Parameter order的HaskellWiki条目和this Stack Overflow问题中,有两条参数顺序建议:

根据我使用Reader / State monad的经验,在不同的环境/状态下调用相同的计算比在相同的环境/状态下调用不同的 monadic计算更频繁。当前的参数顺序很方便。

另一个原因:读取器/状态monadic值的行为非常类似于将环境/初始状态作为参数的函数。在Haskell函数中,在参数之前进行。

为避免嵌套ReaderT / StateT / RWST变换器的必要性,您可以使用一个承载全局状态/环境的RWST转换器,并使用{来自lens库的{1}}和zoom来调整在更受限制的环境中工作的计算。

答案 1 :(得分:3)

我确信这是一种权宜之计。这很容易定义

newtype StateT = StateT { runStateT :: s -> m (a, s) }

并完成它。

答案 2 :(得分:0)

我想添加一致性作为混合的答案。这是所有的推测,所以带上一粒盐:

初始实现使用newtype记录语法来编写这些数据定义,因此数据在运行时首先出现,不知道这是否比作为最后一个参数的数据更可取。较新的数据类型只是遵循这一惯例。