当monad变压器需要提升时?

use*_*134 6 monads haskell 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
Run Code Online (Sandbox Code Playgroud)

所以有一个monad堆栈,其中主要的块有类型ContT Int (StateT Int Identity) Int.

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

Dan*_*zer 11

这里的混乱是因为你正在使用的monad变压器库有点聪明.具体的类型,get并put没有明确提到State或StateT.相反,它们是一致的

get :: MonadState s m => m s
put :: MonadState s m => s -> m ()
Run Code Online (Sandbox Code Playgroud)

因此,只要我们在具有MonadState实现monad 的上下文中使用它,就不需要显式lifts.在您使用get/ putsince的所有实例中都是这种情况

instance MonadState s (StateT s m)
instance MonadState s m => ContT k m
Run Code Online (Sandbox Code Playgroud)

都坚持.换句话说,类型类解析将自动处理为您做适当的提升.这又意味着,你可以在的Elid lift上的S get/ put在你的程序结束.

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


Car*_*arl 6

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

你需要使用lift什么时候lift做东西类型检查,否则不要.

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

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

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