简体   繁体   English

转到Haskell:任何人都可以解释这种看似疯狂的持续monad使用效果吗?

[英]Goto in Haskell: Can anyone explain this seemingly insane effect of continuation monad usage?

From this thread (Control.Monad.Cont fun, 2005), Tomasz Zielonka introduced a function (commented in a clear and nice manner by Thomas Jäger). 这个线程(Control.Monad.Cont fun,2005),Tomasz Zielonka介绍了一个函数(由ThomasJäger以清晰而美观的方式评论)。 Tomasz takes the argument (a function) of a callCC body and returns it for later usage with the following two definitions: Tomasz接受callCC主体的参数(函数)并返回它以供以后使用,具有以下两个定义:

import Control.Monad.Cont
...
getCC :: MonadCont m => m (m a)
getCC = callCC (\c -> let x = c x in return x)

getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))

Those are also mentioned in Haskellwiki . 这些也在Haskellwiki中提到过。 Using them, you can resemble goto semantics in haskell which looks really cool: 使用它们,你可以像haskell中的goto语义看起来非常酷:

import Control.Monad.Cont

getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))

main :: IO ()
main = (`runContT` return) $ do
    (x, loopBack) <- getCC' 0
    lift (print x)
    when (x < 10) (loopBack (x + 1))
    lift (putStrLn "finish")

This prints the numbers 0 to 10. 这将打印数字0到10。

Here comes the interesting point. 这是有趣的一点。 I used this together with the Writer Monad to solve a certain problem. 我和Writer Monad一起使用它来解决某个问题。 My code looks like the following: 我的代码如下所示:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}

import Control.Monad.Cont
import Control.Monad.Writer

getCC :: MonadCont m => m (m a)
getCC = callCC (\c -> let x = c x in return x)

getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))

-- a simple monad transformer stack involving MonadCont and MonadWriter
type APP= WriterT [String] (ContT () IO)

runAPP :: APP a -> IO ()
runAPP a= runContT (runWriterT a) process
      where process (_,w)= do
               putStrLn $ unlines w
               return ()

driver :: Int -> APP ()
driver k = do
   tell [ "The quick brown fox ..." ]
   (x,loop) <- getCC' 0
   collect x
   when (x<k) $ loop (x+1) 

collect :: Int -> APP ()
collect n= tell [ (show n) ] 

main :: IO ()
main = do
   runAPP $ driver 4

When you compile and run this code, the output is: 编译并运行此代码时,输​​出为:

The quick brown fox ...
4

The numbers zero to three are swallowed somewhere in the profound darkness of this example. 在这个例子的深刻黑暗中,数字0到3被吞噬了。

Now, in "Real World Haskell" O'Sullivan, Goerzen and Stewart states 现在,在“真实世界哈斯克尔”奥沙利文,戈尔岑和斯图尔特说

"Stacking monad transformers is analogous to composing functions. If we change the order in which we apply functions and then get different results, we won't be suprised. So it is with monad transformers, too." “堆叠monad变换器类似于组合函数。如果我们改变函数的顺序然后得到不同的结果,我们就不会感到惊讶。所以它也适用于monad变换器。” (Real World Haskell, 2008, P. 442) (Real World Haskell,2008,第442页)

I came up with the idea to swap the transformers above: 我提出了交换以上变压器的想法:

--replace in the above example
type APP= ContT () (WriterT [String] IO)
...
runAPP a = do
    (_,w) <- runWriterT $ runContT a (return . const ())
    putStrLn $ unlines w

However, this won't compile because there is no instance definition for MonadWriter in Control.Monad.Cont (which is why I recently asked this question .) 但是,这不会编译,因为Control.Monad.Cont中没有MonadWriter的实例定义(这就是我最近问过这个问题的原因 。)

We add an instance leaving listen and pass undefined: 我们添加一个实例离开listen并传递undefined:

instance (MonadWriter w m) => MonadWriter w (ContT r m) where
  tell = lift . tell
  listen = undefined
  pass = undefined

Add those lines, compile and run. 添加这些行,编译并运行。 All numbers are printed. 所有数字都打印出来。

What has happened in the previous example? 在前面的例子中发生了什么?

Here's a somewhat informal answer, but hopefully useful. 这是一个有点非正式的答案,但希望有用。 getCC' returns a continuation to the current point of execution; getCC'返回当前执行点的延续; you can think of it as saving a stack frame. 您可以将其视为保存堆栈帧。 The continuation returned by getCC' has not only ContT 's state at the point of the call, but also the state of any monad above ContT on the stack. getCC'返回的延续不仅具有ContT在调用点的状态,而且还具有堆栈上ContT之上的任何monad的状态。 When you restore that state by calling the continuation, all of the monads built above ContT return to their state at the point of the getCC' call. 当你通过调用continuation恢复该状态时,在ContT上面ContT所有monad ContTgetCC'调用时返回到它们的状态。

In the first example you use type APP= WriterT [String] (ContT () IO) , with IO as the base monad, then ContT , and finally WriterT . 在第一个示例中,您使用type APP= WriterT [String] (ContT () IO)IO作为基本monad,然后是ContT ,最后是WriterT So when you call loop , the state of the writer is unwound to what it was at the getCC' call because the writer is above ContT on the monad stack. 因此,当您调用loopgetCC'的状态将解除为getCC'调用时的状态,因为ContT位于monad堆栈上的ContT之上。 When you switch ContT and WriterT , now the continuation only unwinds the ContT monad because it's higher than the writer. 当您切换ContTWriterT ,现在继续只展开ContT monad,因为它高于writer。

ContT isn't the only monad transformer that can cause issues like this. ContT不是唯一可能导致此类问题的monad变换器。 Here's an example of a similar situation with ErrorT 以下是ErrorT类似情况的ErrorT

func :: Int -> WriterT [String] (ErrorT String IO) Int
func x = do
  liftIO $ print "start loop"
  tell [show x]
  if x < 4 then func (x+1)
    else throwError "aborted..."

*Main> runErrorT $ runWriterT $ func 0
"start loop"
"start loop"
"start loop"
"start loop"
"start loop"
Left "aborted..."

Even though the writer monad was being told values, they're all discarded when the inner ErrorT monad is run. 即使作者monad被告知值,当内部ErrorT monad运行时它们都被丢弃了。 But if we switch the order of the transformers: 但是如果我们改变变压器的顺序:

switch :: Int -> ErrorT String (WriterT [String] IO) () 
switch x = do
  liftIO $ print "start loop"
  tell [show x]
  if x < 4 then switch (x+1)
    else throwError "aborted..."

*Main> runWriterT $ runErrorT $ switch 0
"start loop"
"start loop"
"start loop"
"start loop"
"start loop"
(Left "aborted...",["0","1","2","3","4"])

Here the internal state of the writer monad is preserved, because it's lower than ErrorT on the monad stack. 这里保留了ErrorT monad的内部状态,因为它低于monad堆栈上的ErrorT The big difference between ErrorT and ContT is that ErrorT 's type makes it clear that any partial computations will be discarded if an error is thrown. ErrorTContT之间的最大区别在于, ErrorT的类型清楚地表明,如果抛出错误,任何部分计算都将被丢弃。

It's definitely simpler to reason about ContT when it's at the top of the stack, but it is on occasion useful to be able to unwind a monad to a known point. ContT位于堆栈顶部时,对于ContT推理肯定更简单,但有时能够将monad展开到已知点是有用的。 A type of transaction could be implemented in this manner, for example. 例如,可以以这种方式实现一种交易。

I spent some time tracing this in the λ calculus. 我花了一些时间在λ演算中追踪这个。 It generated pages and pages of derivations that I won't attempt to reproduce here, but I did gain a little insight on how the monad stack works. 它生成了我不会尝试在这里重现的派生页面和页面,但我确实对monad堆栈的工作方式有了一些了解。 Your type expands as follows: 您的类型扩展如下:

type APP a = WriterT [String] (ContT () IO) a
           = ContT () IO (a,[String])
           = ((a,[String]) -> IO()) -> IO()

You can similarly expand out Writer's return , >>= , and tell , along with Cont's return , >>= , and callCC . 您可以类似地扩展Writer的return>>= ,并tell Cont,以及Cont的return>>=callCC Tracing it is extremely tedious though. 追查它是非常乏味的。

The effect of calling loop in the driver is to abandon the normal continuation and instead return, again, from the call to getCC' . 在驱动程序中调用loop的效果是放弃正常的延续,而是再次从调用getCC' That abandoned continuation contained the code that would have added the current x to the list. 那个放弃的延续包含了将当前x添加到列表中的代码。 So instead, we repeat the loop, but now x is the next number, and only when we hit the last number (and thus don't abandon the continuation) do we piece together the list from ["The quick brown fox"] and ["4"] . 所以相反,我们重复循环,但现在x是下一个数字,只有当我们点击最后一个数字(因此放弃延续)时,我们将["The quick brown fox"]的列表拼凑在一起, ["4"]

Just as “Real World Haskell” emphasizes that the IO monad needs to stay on the bottom of the stack, it also seems important that the continuation monad stays on top. 就像“真实世界Haskell”强调IO monad需要保持在堆栈的底部一样,延续monad仍然保持领先地位似乎也很重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM