如何在Haskell中提取分隔接续(reset/shift)以供将来使用?

l7wslrjt  于 2022-11-14  发布在  其他
关注(0)|答案(2)|浏览(106)

以下是使用分隔接续(reset/shift)的简单范例:

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Cont

test :: Integer
test = evalCont . reset $ do
    r <- shift $ \k -> do
        return $ k 10
    return $ 1 + r

λ> test1
11

它工作得很好。
但是,我想将continuation k提取为一个纯函数以备将来使用,而不是在shift中调用它。
例如,我希望test2可以返回k

test2 :: Integer -> Integer
test2 = evalCont . reset $ do
    r <- shift $ \k -> do
        return $ k
    return $ 1 + r

但GHC抱怨道:

? Couldn't match type 'Integer -> Integer' with 'Integer'
      Expected type: Cont (Integer -> Integer) (Integer -> Integer)
        Actual type: ContT
                       (Integer -> Integer)
                       Data.Functor.Identity.Identity
                       ((Integer -> Integer) -> Integer -> Integer)
    ? In a stmt of a 'do' block: return $ k
      In the expression: do return $ k
      In the second argument of '($)', namely '\ k -> do return $ k'
   |
88 |         return $ k
   |         ^^^^^^^^^^

有人能帮我解决这个问题吗?

  • 谢谢-谢谢
6yt4nkrj

6yt4nkrj1#

标准的Cont是不完全通用的。

newtype Cont    i o a =    Cont { runCont :: (a -> i) -> o }
-- versus the standard
newtype SadCont   r a = SadCont { sadCont :: (a -> r) -> r }
-- SadCont r a = Cont r r a

使用标准SadCont是因为它支持常用类型的>>=return(因此它可以是Monad)。但是Cont内部的“真实的”定界延续允许每个shift从一种类型的延续中取值,并将它们向上发送到前一个不同类型的shift/reset。在本例中,您只是将 entire continuation作为函数从shift传递到reset

{-# LANGUAGE RebindableSyntax #-}
-- ^ placing this at the top of a file or passing -XRebindableSyntax to GHC allows do notation to use custom (>>=) and (>>)

-- not Monad operations!
return :: a -> Cont r r a
return x = Cont ($ x)
(>>=) :: Cont m o a -> (a -> Cont i m b) -> Cont i o b
Cont x >>= f = Cont $ \k -> x (($ k) . runCont . f)
(>>) :: Cont m o a -> Cont i m b -> Cont i o b -- RebindableSyntax also wants this
a >> b = a >>= const b

evalCont :: Cont a o a -> o
evalCont (Cont x) = x id

-- shift/reset are actually just
reset = evalCont
shift = Cont
-- note that the types of reset and shift differ significantly from transformers
-- reset returns a pure value here and shift requires a pure value from its function
-- I think my choices are more correct/standard, e.g. they line up with the old Scala shift/reset http://lampwww.epfl.ch/~hmiller/scaladoc/library/scala/util/continuations/package.html

在您的示例中

test2 :: Integer -> Integer
test2 = reset $ do
    r <- shift $ \k -> k
    return $ 1 + r

TL;DR Cont被故意“破坏”了,所以它失去了不同输入和输出类型的通用性,但却获得了Monad的特性。你可以通过将输入和输出类型放入一个(递归)和中来解决这个问题。或者(这个答案)你可以定义并使用“真实的”Cont

drkbr07n

drkbr07n2#

受到@BenjaminHodgson评论的启发,以下是临时解决方案:

data Ret a = Fun (Integer -> Ret a) | Val a

instance Show a => Show (Ret a) where
    show (Fun f) = "Jst f"
    show (Val a) = show a

test2 :: Ret Integer
test2 = evalCont . reset $ do
    r <- shift $ \k -> do
         return $ Fun k
    return $ Val (1 + r)

main :: IO ()
main = do
        print $ case test2 of (Fun f) -> f 100
        print $ case test2 of (Fun f) -> f 50

λ> main
101
51

免责声明:我不确定递归类型Ret是否必要。
如果有人能提供更好的解决方案或解释,我将不胜感激。谢谢。

相关问题