Cont monad shift

时间:2017-04-29 12:20:43

标签: haskell continuations delimited-continuations

在尝试为ContT monad变换器构建一些直觉时,我(也许不出所料)发现自己很困惑。问题在于shiftT操作似乎没有任何用处。

首先是一个如何使用它的简单例子

shiftT $ \famr -> lift $ do
  a <- calculateAFromEnvironment
  famr a
只要它返回一些famr a

m r可能是一些更复杂的表达式。现在试图解释我对shiftT的直觉并没有增加任何东西:

-- inline shiftT
ContT (\f2 -> evalContT ((\f1 -> lift (do
  a <- calculateAFromEnvironment
  f1 a)) f2))

-- beta reduction
ContT (\f2 -> evalContT (lift (do
  a <- calculateAFromEnvironment
  f2 a)))

-- inline evalConT
ContT (\f2 -> runContT (lift (do
  a <- calculateAFromEnvironment
  f2 a)) return)

-- inline lift
ContT (\f2 -> runContT (ContT (\f3 -> (do
  a <- calculateAFromEnvironment
  f2 a) >>= f3)) return)

-- apply runConT
ContT (\f2 -> (\f3 -> (do
  a <- calculateAFromEnvironment
  f2 a) >>= f3) return)

-- beta reduce
ContT (\f2 -> (do
  a <- calculateAFromEnvironment
  f2 a) >>= return)

-- (>>= return) is identity
ContT $ \f2 -> do
  a <- calculateAFromEnvironment
  f2 a

原来我们可以直接构建ContT。

提问时间:是否存在shift / shiftT在cont / ContT上添加任何内容的情况?或者它们只是用于使代码更具可读性?

2 个答案:

答案 0 :(得分:4)

searching github Gurkenglas的建议之后,我发现了shiftT resetT resetT :: (Monad m) => ContT r m r -> ContT r' m r resetT = lift . evalContT shiftT :: (Monad m) => ((a -> m r) -> ContT r m r) -> ContT r m a shiftT f = ContT (evalContT . f) shiftT的用例,动机示例和语义!

这些功能非常简单。他们在this very nice explanation库中的定义很简单:

shiftT

但哲学和意义远远落后于一些直观的理解。所以我建议你阅读上面链接的解释。有时,实际上很容易定义的东西可以做一些复杂的事情。

根据以上链接的哈巴狗的解释改编文件:

  

callCC

     

shiftTresetT类似,但激活延续时除外   由callCC提供,它将运行到最近的封闭resetT的末尾,   然后跳回到激活延续的点之后。   请注意,因为控件最终会返回到之后的点   subcontinuation已激活,您可以在中激活多次   相同的块。这与resetT的延续不同,它延续了当前的延续   激活时执行路径。

     

有关这些分隔子连续实际如何的示例,请参阅shiftT   工作

     

resetT $ do alfa bravo x <- shiftT $ \esc -> do -- note: esc :: m Int, not a ContT charlie lift $ esc 1 delta lift $ esc 2 return 0 zulu x

     

创建一个范围,最终保证alfa的子连续   退出结束。考虑这个例子:

bravo
     

这将:

     
      
  1. 执行charlie

  2.   
  3. 执行x

  4.   
  5. 执行zulu 1

  6.   
  7. resetT绑定为1,从而执行esc 1

  8.   
  9. delta结束,然后跳回x

  10. 之后   
  11. 执行zulu 2

  12.   
  13. resetT绑定为2,从而执行esc 2

  14.   
  15. resetT结束,然后跳回callCC

  16. 之后   
  17. 逃离resetT,导致其产生0

  18.         

    因此,与constexpr的延续不同,这些子连续最终将会延续   在它们被激活之后返回到它之后   最近decltype(...){}

答案 1 :(得分:2)

你是对的delimited continuations可以使用无限延续来表达。因此,shiftTresetT的定义始终只能使用ContT来描述。但是:

  • 定界延续为less powerful。这使得它们更容易实现,也适用于人类。 (另请参阅Oleg Kiselyov的许多其他有趣的posts about continuations)。
  • 使用熟悉的 shift / reset 符号可以让您更容易理解,特别是对于那些熟悉这个概念的人。

本质上,continuation允许将程序内部翻出来:当reset调用传递的函数时,由shift分隔的块被挤压到程序的内部部分。 (在无限延续的情况下,整个执行上下文被挤入内部,这就是使它们如此奇怪的原因。)

我们举几个例子:

import Data.List
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Cont

test0 :: Integer
test0 = evalCont . reset $ do
    return 0

如果我们reset没有shift,那只是一个纯粹的计算,没什么特别的。上述函数只返回0

现在让我们使用它们:

test1 :: Integer
test1 = evalCont . reset $ do
    r <- shift $ \esc -> do
        let x = esc 2
            y = esc 3
        return $ x * y
    return $ 1 + r

这变得更有趣。 shiftreset之间的代码实际上被压缩到esc的调用中,在这个简单的示例中它只是return $ 1 + r。当我们调用esc时,将执行整个计算,其结果将成为esc调用的结果。我们这样做了两次,所以基本上我们两次调用shiftreset之间的所有内容。整个计算的结果是result $ x * yshift调用的结果。

从某种意义上说,shift块成为计算的外部部分,resetshift之间的块成为计算的内部部分。

到目前为止一切顺利。但是如果我们两次调用shift,就会变得更加艰巨,就像在这个代码示例中一样:

list2 :: [(Int, String)]
list2 = evalCont . reset $ do
    x <- shift $ \yieldx ->
        return $ concatMap yieldx [1, 2, 3]
    y <- shift $ \yieldy ->
        return $ concatMap yieldy ["a", "b", "c"]
    return [(x, y)]

这就是它产生的东西(对那些想要试图把它弄清楚作为练习的人来说是隐藏的):

  

[(1,"a"),(1,"b"),(1,"c"),(2,"a"),(2,"b"),(2,"c"),(3,"a"),(3,"b"),(3,"c")]

现在发生的事情是该程序内外输出两次

  1. 首先x <- shift ...块之外的所有内容都绑定到yieldx来电,包括下一个shift。计算结果是x <- shift ...块的结果。
  2. 其次,当调用y <- shift ...内的第二个yieldx时,计算的其余部分再次绑定到yieldy调用。此内部计算的结果是y <- shift ...块的结果。
  3. 因此,在x <- shift中,我们为三个参数中的每一个运行剩余的计算,并且在每个参数中,我们对其他三个参数执行类似的操作。结果是两个列表的笛卡尔积,因为我们基本上执行了两个嵌套循环。

    同样的事情适用于shiftTresetT,只是增加了副作用。例如,如果我们想调试实际发生的事情,我们可以在IO monad中运行上面的代码并打印调试语句:

    list2' :: IO [(Int, String)]
    list2' = evalContT . resetT $ do
        x <- shiftT $ \yield ->
            lift . liftM concat . mapM (\n -> print n >> yield n) $ [1, 2, 3]
        y <- shiftT $ \yield ->
            lift . liftM concat . mapM (\n -> print n >> yield n) $ ["a", "b", "c"]
        return [(x, y)]