monad transformer 到底什么时候需要提升?
When exactly is lifting needed in monad transformers?
我正在学习 monad 转换器,我对何时需要使用 lift 感到困惑。
假设我有以下代码(它没有做任何有趣的事情,只是我可以用来演示的最简单的代码)。
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 堆栈,其中主 do 块的类型为 ContT Int (StateT Int Identity) Int
。
现在,在带有递归的第三个 when
do 块中,程序编译需要提升。在第二个块中,不需要提升,但我以某种方式假设这是因为 exit
的存在,它以某种方式迫使线上方的线被提升到 ContT
。但在第一个街区,不需要电梯。 (但如果明确添加,也没有问题。)这真是让我感到困惑。我觉得所有 when
do 块都是等效的,要么到处都需要升降机,要么什么地方都不需要升降机。但这显然不是真的。需要提升 required/not 的关键区别在哪里?
这里的混乱是因为您使用的 monad 转换器库有点聪明。具体来说,get
和 put
的类型没有明确提及 State
或 StateT
。相反,它们是沿着线
get :: MonadState s m => m s
put :: MonadState s m => s -> m ()
因此,只要我们在 MonadState
实现 monad 的上下文中使用它,就不需要显式的 lift
s。在您使用 get
/put
的所有情况下都是这种情况,因为
instance MonadState s (StateT s m)
instance MonadState s m => ContT k m
两者都成立。换句话说,类型 class 分辨率将自动为您处理适当的提升。这反过来意味着您可以在程序末尾省略 get
/put
上的 lift
。
递归调用不会发生这种情况,因为它的类型明确 State Int Int
。如果将其概括为 MonadState Int m => m Int
,您甚至可以省略最后的升力。
我想提供一个替代答案,既肤浅又重要。
当 lift
进行类型检查时,您需要使用 lift
。
是的,这听起来很肤浅,似乎缺乏任何深层含义。但事实并非如此。 MonadTrans
是 class 的东西,可以以中立的方式将 monadic 动作提升到更大的上下文中。如果您需要技术说明,class 法则就 "neutral" 的含义提供了更明确的规则。但结果是 lift
除了使提供的操作与另一种类型兼容所必需的之外,什么都不做。
那么 - lift
是做什么的?它提供了将 monadic 动作提升为更大类型所必需的逻辑。你什么时候需要使用它?当你有一个 monadic 动作时,你需要提升到一个更大的类型。你什么时候需要将一个 monadic 动作提升为更大的类型?当这就是类型告诉你的时候。
这是使用Haskell的关键部分。你可以模块化你对代码的理解。类型系统为您跟踪大量簿记。依靠它来正确记账,因此您只需要将逻辑记在脑海中即可。编译器和类型系统在那里充当精神放大器。他们处理的越多,您在编写软件时需要考虑的事情就越少。
我正在学习 monad 转换器,我对何时需要使用 lift 感到困惑。 假设我有以下代码(它没有做任何有趣的事情,只是我可以用来演示的最简单的代码)。
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 堆栈,其中主 do 块的类型为 ContT Int (StateT Int Identity) Int
。
现在,在带有递归的第三个 when
do 块中,程序编译需要提升。在第二个块中,不需要提升,但我以某种方式假设这是因为 exit
的存在,它以某种方式迫使线上方的线被提升到 ContT
。但在第一个街区,不需要电梯。 (但如果明确添加,也没有问题。)这真是让我感到困惑。我觉得所有 when
do 块都是等效的,要么到处都需要升降机,要么什么地方都不需要升降机。但这显然不是真的。需要提升 required/not 的关键区别在哪里?
这里的混乱是因为您使用的 monad 转换器库有点聪明。具体来说,get
和 put
的类型没有明确提及 State
或 StateT
。相反,它们是沿着线
get :: MonadState s m => m s
put :: MonadState s m => s -> m ()
因此,只要我们在 MonadState
实现 monad 的上下文中使用它,就不需要显式的 lift
s。在您使用 get
/put
的所有情况下都是这种情况,因为
instance MonadState s (StateT s m)
instance MonadState s m => ContT k m
两者都成立。换句话说,类型 class 分辨率将自动为您处理适当的提升。这反过来意味着您可以在程序末尾省略 get
/put
上的 lift
。
递归调用不会发生这种情况,因为它的类型明确 State Int Int
。如果将其概括为 MonadState Int m => m Int
,您甚至可以省略最后的升力。
我想提供一个替代答案,既肤浅又重要。
当 lift
进行类型检查时,您需要使用 lift
。
是的,这听起来很肤浅,似乎缺乏任何深层含义。但事实并非如此。 MonadTrans
是 class 的东西,可以以中立的方式将 monadic 动作提升到更大的上下文中。如果您需要技术说明,class 法则就 "neutral" 的含义提供了更明确的规则。但结果是 lift
除了使提供的操作与另一种类型兼容所必需的之外,什么都不做。
那么 - lift
是做什么的?它提供了将 monadic 动作提升为更大类型所必需的逻辑。你什么时候需要使用它?当你有一个 monadic 动作时,你需要提升到一个更大的类型。你什么时候需要将一个 monadic 动作提升为更大的类型?当这就是类型告诉你的时候。
这是使用Haskell的关键部分。你可以模块化你对代码的理解。类型系统为您跟踪大量簿记。依靠它来正确记账,因此您只需要将逻辑记在脑海中即可。编译器和类型系统在那里充当精神放大器。他们处理的越多,您在编写软件时需要考虑的事情就越少。