你想玩游戏吗?
今天,您将成为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
所以很明显,我们需要发挥r3
或a
取得任何进展。
玩a
:如果我们玩a
,r2
那么我们可以id
玩a -> r2
。另一个动作更复杂,所以我们会跳进去。
b => r1 => a -> (b -> r1) -> r1
回到代表我们的最右边的箭头。这一次我们需要产生某种类型的东西,即对手的移动在r1
哪里。r1
他们还发挥了作用b -> r1
,其中类型b
也是他们做出的举动。所以我们需要任何一种类型b
或r1
来自他们的东西。不幸的是,他们给我们的只是某种类型的东西a
,使我们处于无法取胜的境地。猜猜a
早点玩是一个糟糕的选择。让我们再试一次...
播放r3
:如果我们播放r3
为r2
,我们还需要播放一个功能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
被对手选择。所以我们在这里所做的只是把事情改组;显然,我们甚至无法希望得到或摆脱这种情况,所以我们又遇到了死胡同。a
r3
所以我们做了最后一次绝望的尝试:
打破规则——玩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..."
唉,似乎唯一的制胜法宝就是不玩。