当monad变压器需要提升时?

时间:2017-07-21 11:13:38

标签: haskell monads monad-transformers lifting

我正在学习monad变形金刚,我很困惑何时需要使用升降机。 假设我有以下代码(它没有做任何有趣的事情,只是我可以带来的最简单的演示)。

foo :: Int -> State Int Int
foo x = do
  (`runContT` pure) $ do
    callCC $ \exit -> do
      when (odd x) $ do
        -- lift unnecessary
        a <- get
        put $ 2*a
      when (x >= 5) $ do
        -- lift unnecessary, but there is exit 
        a <- get
        exit a
      when (x < 0) $ do
        -- lift necessary
        a <- lift $ foo (x + 10)
        lift $ put a

      lift get

所以有一个monad堆栈,其中main do块的类型为ContT Int (StateT Int Identity) Int

现在,在递归的第三个when块中,需要提升程序才能编译。在第二个区块中,不需要升力,但我不知何故假设它是因为exit的存在,它以某种方式迫使线上方的线被提升到ContT。但在第一个区块,不需要升力。 (但如果明确添加,也没有问题。)这对我来说真的很困惑。我觉得所有的when块都是等价的,无论是在任何地方还是在任何地方都需要电梯。但这显然不是真的。需要/不需要升降机的主要区别在哪里?

2 个答案:

答案 0 :(得分:11)

这里出现了混乱,因为你使用的monad变压器库有点聪明。具体而言,getput的类型并未明确提及StateStateT。相反,它们是沿着

get :: MonadState s m => m s
put :: MonadState s m => s -> m ()

因此,只要我们在实现monad MonadState的上下文中使用它,就不需要显式的lift。从

开始使用get / put的所有情况都是如此
instance MonadState s (StateT s m)
instance MonadState s m => ContT k m
双方都持有。换句话说,类型类解析将自动处理为您做适当的提升。这反过来意味着您可以在计划结束时忽略lift / get上的put

您的递归调用不会发生这种情况,因为它的类型是显式的State Int Int。如果你把它推广到MonadState Int m => m Int,你甚至可以忽略这个最后的提升。

答案 1 :(得分:6)

我想提供一个替代的答案,既肤浅又重要的一切。

lift进行类型检查时,您需要使用lift否则不会。

是的,这听起来很肤浅,似乎没有任何深刻的含义。但那不是真的。 MonadTrans是一类可以将monadic动作以中性方式提升到更大背景的事物。如果你想要技术描述,那么阶级法则提供关于“中立”意味着什么的更明确的规则。但结果是lift除了使提供的动作与另一种类型兼容之外什么都不做。

那么 - lift做什么?它提供了将monadic动作提升为更大类型所需的逻辑。你什么时候需要使用它?当你有一个monadic动作,你需要提升到一个更大的类型。你什么时候有一个monadic动作,你需要提升到更大的类型?这就是类型告诉你的。

这是使用Haskell的关键部分。您可以模块化您对代码的理解。类型系统为您记录大量的簿记。依靠它来使记账正确,所以你只需要保持头脑中的逻辑。编译器和类型系统可作为智能放大器使用。他们照顾的越多,在编写软件时就越不需要保持头脑。