22

我认为 ContT 的正确类型应该是

newtype ContT m a = ContT {runContT :: forall r. (a -> m r) -> m r}

和其他控制操作员

shift :: Monad m => (forall r. (a -> ContT m r) -> ContT m r) -> ContT m a
reset :: Monad m => ContT m a -> ContT m a
callCC :: ((a -> (forall r. ContT m r)) -> ContT m a) -> ContT m a

不幸的是,我无法进行callCC类型检查,也不知道该怎么做。我设法进行了shift类型reset检查

reset :: Monad m => ContT m a -> ContT m a
reset e = ContT $ \ k -> runContT e return >>= k

shift :: Monad m => (forall r. (a -> ContT m r) -> ContT m r) -> ContT m a
shift e = ContT $ \ (k :: a -> m r) ->
    runContT ((e $ \ v -> ContT $ \c -> k v >>= c) :: ContT m r) return

但是,我仍然不能在这样的递归跳跃中使用shift和?reset

newtype H r m = H (H r m -> ContT m r)

unH (H x) = x

test = flip runContT return $ reset $ do
    jump <- shift (\f -> f (H f))
    lift . print $ "hello"
    unH jump jump

有没有人试过这个?

4

2 回答 2

30

想玩游戏吗?

今天,您将成为callCC.

callCC :: ((a -> (forall r. ContT m r)) -> ContT m a) -> ContT m a
--                                       you are here ^^

该功能箭头左侧的所有内容都是您的对手所做的动作。箭头的右边是游戏的结束。要获胜,您必须仅使用对手提供的棋子构建与右侧匹配的东西。

幸运的是,你在事情上仍有发言权。看到这个箭头了吗?

callCC :: ((a -> (forall r. ContT m r)) -> ContT m a) -> ContT m a
--                this is your opponent ^^

当您收到本身包含箭头的东西时,左侧的所有内容都代表您要做出的动作右侧的部分代表该游戏分支的末端,为您提供另一块可以用作(希望)的一部分制胜战略。

在我们进一步讨论之前,让我们简化一些事情: monad 转换器方面只是一种干扰,所以放弃它;并为每个类型变量添加显式量词。

callCC :: forall a. ((a -> (forall b. Cont b)) -> Cont a) -> Cont a

现在,考虑一下 like 的类型forall a. ...。如果您生成具有这种类型的东西,您就是说您可以为任何类型提供值a。如果您收到类似类型的东西,您可以选择要使用的特定类型。A -> ...将其与单态函数之类的类型进行比较;生成这样的函数表示您知道如何为任何类型的值提供结果A,而接收这样的函数可以让您选择要使用的特定值A。这似乎与 的情况相同forall,实际上平行成立。因此,我们可以将forall其视为指示您或您的对手可以玩某种类型的动作,而不是一个术语。为了反映这一点,我将滥用符号并将其写forall a. ...a =>; 然后我们可以像对待它一样对待它,只是(->)它必须出现在被绑定的类型变量的任何使用的左侧。

我们还可以注意到,唯一可以直接使用类型值完成的事情Cont a就是应用runCont它。所以我们会提前这样做,并将所有相关的量词直接嵌入到 for 类型中callCC

callCC :: a => ( (a -> (b => (r1 => (b -> r1) -> r1))) 
               -> (r2 => (a -> r2) -> r2)
               ) -> (r3 => (a -> r3) -> r3)

因为我们能够forall像其他函数箭头一样对待,我们可以重新排序并删除多余的括号以整理一下。特别要注意的callCC是,事实证明这实际上并不是游戏的结束;我们必须提供一个功能,这相当于提供另一个游戏,我们再次扮演最右边的箭头的角色。所以我们可以通过合并这些来节省一步。我还将向左浮动类型参数以将它们全部放在一个位置。

callCC :: a => r3 => (a -> r3) 
       -> (r2 => (b => r1 => a -> (b -> r1) -> r1) -> (a -> r2) -> r2) 
       -> r3

所以……我们的举动。

我们需要某种类型的东西r3。我们的对手已经采取了四步棋,我们收到了这些棋步作为论据。一招就是选择r3,所以我们已经处于劣势了。另一个动作是a -> r3,意思是如果我们能打出a,我们的对手会咳出一个r3,我们就可以顺利获胜。不幸的是,我们的对手打了a,所以我们又回到了开始的地方。我们要么需要某种类型的东西,要么需要a某种其他方式来获得某种类型的东西r3

我们对手的最后一步更复杂,所以我们将单独研究它:

r2 => (b => r1 => a -> (b -> r1) -> r1) -> (a -> r2) -> r2

请记住,这是他们采取的行动。所以这里最右边的箭头代表我们的对手,左边的一切都代表我们可以采取的行动类型。这样做的结果是某种类型的东西,我们可以r2在哪里玩。r2所以很明显,我们需要发挥r3a取得任何进展。

a如果我们玩ar2那么我们可以ida -> r2。另一个动作更复杂,所以我们会跳进去。

b => r1 => a -> (b -> r1) -> r1

回到代表我们的最右边的箭头。这一次我们需要产生某种类型的东西,即对手的移动在r1哪里。r1他们还发挥了作用b -> r1,其中类型b也是他们做出的举动。所以我们需要任何一种类型br1来自他们的东西。不幸的是,他们给我们的只是某种类型的东西a,使我们处于无法取胜的境地。猜猜a早点玩是一个糟糕的选择。让我们再试一次...

播放r3如果我们播放r3r2,我们还需要播放一个功能a -> r3;好在对手已经发挥了这样的功能,所以我们可以简单地使用它。我们再一次跳进另一个动作:

b => r1 => a -> (b -> r1) -> r1

……才发现这和以前完全一样不可能的情况。任由对手选择,r1而不要求他们提供一种构建方法,这让我们陷入了困境。

也许有点诡计会有所帮助?

打破规则——玩r1

我们知道,在常规的 Haskell 中,我们可以依靠懒惰来扭曲事物,让计算吞下自己的尾巴。不用太担心怎么做,让我们想象一下我们也可以在这里做同样的事情——拿r1我们的对手在内心游戏中玩的 ,然后把它拉出来然后拉回来玩它r2

再一次,这是对手的举动:

r2 => (b => r1 => a -> (b -> r1) -> r1) -> (a -> r2) -> r2

在我们的打结恶作剧之后,它最终等同于:

(b => a -> (b -> r1) -> r1) -> (a -> r1) -> r1

由于我们的诡计,类型参数已经消失,但仍然r1被对手选择。所以我们在这里所做的只是把事情改组;显然,我们甚至无法希望得到或摆脱这种情况,所以我们又遇到了死胡同。ar3

所以我们做了最后一次绝望的尝试:

打破规则——玩b

这次我们取b对手在最里面的游戏中玩的,然后循环播放为r2。现在对手的动作是这样的:

(r1 => a -> (b -> r1) -> r1) -> (a -> b) -> b

然后回到内心游戏:

r1 => a -> (b -> r1) -> r1

继续这个诡计,我们也可以扭曲b上面的结果,给函数b -> r1,接收r1我们需要的。成功!

退后一步,我们还有一个问题。我们必须玩一些类型的东西a -> b。没有明显的方法可以找到,但我们已经有一个b谎言,所以我们可以用const它来丢弃和a——

- 可是等等。类型的值b首先来自哪里?把我们自己简单地设在对手的位置上,他们唯一能得到的地方就是我们所采取的行动的结果。如果b我们只有他们给我们的,我们最终会绕着圈子转;游戏永远不会结束。


因此,在 的游戏中callCC,我们仅有的策略会导致失败或永久陷入僵局。

callCC :: forall a. ((a -> (forall b . Cont b)) -> Cont a) -> Cont a
callCC = error "A strange game..."

唉,似乎唯一的制胜法宝就是不玩。

于 2011-08-24T18:05:36.607 回答
1

虽然没有办法赢得给定的比赛,但如果我们能稍微扭转一下比赛,我们就能赢!

newtype ContT' m a =
    ContT' { runContT' :: forall r. (Maybe a -> m (Maybe r)) -> m (Maybe r) }

正如我们在另一个答案中看到的那样,我们遇到的问题是,为了获胜,我们必须为对手玩的任意类型生成一个值,但我们不知道如何去做。

通过强制所有原始类型 (ra) 由 修饰Maybe,我们可以绕过这个问题,并且能够生成任何值Maybe t- 只需给Nothing它们!

我们必须证明这是一个Monad.

instance Monad m => Monad (ContT' m) where
    return a = ContT' $ \k -> k (Just a)
    a >>= f = ContT' $ \c -> runContT' a (
        maybe (return Nothing) (\v -> runContT' (f v) c)) 

然后我们可以实现callCC

class Monad m => MonadCont' m where
    callCC' :: ((a -> forall b.m b) -> m a) -> m a

instance Monad m => MonadCont' (ContT' m) where
    callCC' k = ContT' $ \c -> 
        runContT' (k (\a -> ContT' $ \_ -> c (Just a) >> return Nothing)) c  

我仍在研究如何实施resetshift.

于 2014-06-17T11:56:54.333 回答