转到Haskell:任何人都可以解释继续monad使用这个看起来疯狂的效果吗? [英] Goto in Haskell: Can anyone explain this seemingly insane effect of continuation monad usage?

查看:72
本文介绍了转到Haskell:任何人都可以解释继续monad使用这个看起来疯狂的效果吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个线程(Control.Monad.Cont fun,2005),Tomasz Zielonka介绍了一个功能(ThomasJäger以一种清晰而美好的方式评论)。 Tomasz接受callCC主体的参数(函数)并将其返回以供以后使用,具有以下两个定义:

  import控制.Monad.Cont 
...
getCC :: MonadCont m => m(m a)
getCC = callCC(\ c - > let x = c x作为回报x)

getCC':: MonadCont m => a - > m(a,a - > mb)
getCC'x0 = callCC(\c->让fx = c(x,f)作为回报(x0,f))

这些也在 Haskellwiki 。使用它们,你可以像Haskell中的goto semantics看起来很酷:



pre $ import Control.Monad.Cont

getCC':: MonadCont m => a - > m(a,a - > mb)
getCC'x0 = callCC(\c->让fx = c(x,f)作为回报(x0,f))

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

这会打印0到10的数字。



这里有个有趣的地方。我和Writer Monad一起使用它来解决某个问题。我的代码如下所示:

  { - #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作为回报x)

getCC':: MonadCont m => a - > m(a,a - > mb)
getCC'x0 = callCC(\c->让fx = c(x,f)作为回报(x0,f))

- 包含MonadCont和MonadWriter的简单monad变量堆栈
type APP = WriterT [String](ContT()IO)

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

driver :: Int - > APP()
driver k = do
tell [快速棕色狐狸...]
(x,loop)< - getCC'0
collect x
当(x
collect :: Int - > APP()
collect n = tell [(show n)]

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

当您编译并运行此代码时,输​​出为:

 快速棕色狐狸... 
4



<在这个例子的深度黑暗中,零到三个数字被吞噬。

现在,在真实世界Haskell中,O'Sullivan,Goerzen和Stewart指出, / p>

堆叠monad变换器类似于组合函数,如果我们改变函数的顺序并得到不同的结果,我们不会感到惊讶。 (真实世界哈斯克尔,2008年,第442页)



我想出了将变压器换掉的想法上面的代码:

   - 替换上面的例子
type APP = ContT()(WriterT [String] IO)
...
runAPP a = do
(_,w)< - runWriterT $ runContT a(return。 const())
putStrLn $ unlines w

然而,这不会编译,因为那里在Control.Monad.Cont中没有MonadWriter的实例定义(这就是为什么我最近要求

我们添加一个离开listen和pass的实例undefined:

  instance(MonadWriter wm)=> MonadWriter w(ContT r m)其中
tell = lift。告诉
listen = undefined
pass = undefined

添加这些行,编译和跑。所有的数字都被打印出来。



在前面的例子中发生了什么?

解决方案

这是一个有点非正式的答案,但希望有用。 getCC'返回到当前执行点的延续;你可以认为它是保存一个堆栈帧。由 getCC'返回的延续不仅在调用点处 ContT 的状态,而且还有状态任何超过 ContT 的单子。当您通过调用continuation来恢复该状态时,所有在 ContT 之上构建的monad将返回到它们在 getCC' call。



在第一个示例中,您使用 type APP = WriterT [String](ContT()IO),其中 IO 作为基准monad,然后 ContT ,最后 WriterT 。所以,当你调用 loop 时,编写器的状态会解开它在 getCC'调用中的状态,因为编写者在monad堆栈上的 ContT 之上。当您切换 ContT WriterT 时,现在继续只展开 ContT monad,因为它比作者高。

ContT 不是唯一可能导致像这样的问题。下面是一个类似情况的例子: ErrorT

  func :: Int - > WriterT [String](ErrorT String IO)Int 
func x = do
liftIO $ printstart loop
tell [show x]
if x< 4然后func(x + 1)
else throwErroraborted ...

* Main> runErrorT $ runWriterT $ func 0
开始循环
开始循环
开始循环
开始循环
开始循环
左中止......

即使作者monad被告知值,他们都是当内部 ErrorT monad运行时丢弃。但是如果我们切换变换器的顺序:

  switch :: Int  - > ErrorT String(WriterT [String] IO)()
switch x = do
liftIO $ printstart loop
tell [show x]
if x< 4然后切换(x + 1)
else throwErroraborted ...

* Main> runWriterT $ runErrorT $ switch 0
开始循环
开始循环
开始循环
开始循环
开始循环
(左中止...,[0,1,2,3,4])

在这里,写入者monad的内部状态被保留下来,因为它低于monad堆栈中的 ErrorT ErrorT ContT 之间的巨大区别在于 ErrorT ' s类型清楚地表明,如果发生错误,任何部分计算都将被丢弃。



推理 ContT 当它位于堆栈的顶部时,但它有时能够将monad展开到已知点。例如,一种交易可以用这种方式实现。


From this thread (Control.Monad.Cont fun, 2005), Tomasz Zielonka introduced a function (commented in a clear and nice manner by Thomas Jäger). Tomasz takes the argument (a function) of a callCC body and returns it for later usage with the following two definitions:

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. Using them, you can resemble goto semantics in haskell which looks really cool:

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.

Here comes the interesting point. I used this together with the Writer Monad to solve a certain problem. 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.

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." (Real World Haskell, 2008, P. 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.)

We add an instance leaving listen and pass 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; 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. 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.

In the first example you use type APP= WriterT [String] (ContT () IO), with IO as the base monad, then ContT, and finally 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. When you switch ContT and WriterT, now the continuation only unwinds the ContT monad because it's higher than the writer.

ContT isn't the only monad transformer that can cause issues like this. Here's an example of a similar situation with 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. 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. 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.

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. A type of transaction could be implemented in this manner, for example.

这篇关于转到Haskell:任何人都可以解释继续monad使用这个看起来疯狂的效果吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆