想象一下你为主流语言编码器(JS、Java、Python)解释这个
你不能。你真的不能,因为你真的不能用 Java 写那行代码。
let fn = out fn
in return fn
fn 不是隐藏变量或任何东西。它在那里宣布。它的值为out fn。变量fn 是根据自身定义的。听起来很荒谬,但我们一直在 Haskell 中这样做。例如,
let xs = 1 : xs in ...
这是一个无限列表。变量xs 被定义为其自身,并在前面加上1。唯一的[1]为真的值是无限数量的列表。实际上,我们找到了一个固定点.
回到你的例子,
goto = C.callCC $ out -> let fn = out fn
in return fn
callCC 的类型是
callCC :: ((a -> ContT r m b) -> ContT r m a) -> ContT r m a
我们在这里没有对 monad 转换器做任何疯狂的事情,所以让我们删除 m。
callCC :: ((a -> Cont r b) -> Cont r a) -> Cont r a
因此,out的类型必须是out :: a -> Cont r b(对于某些r,a, and b`)。现在,我们写了
let fn = out fn in return fn
整个表达式的类型为Cont r a。因此,return fn 的类型为 Cont r a 和 return :: Monad m => a -> m a,所以 fn :: a。
我们将 out 应用到 fn,所以
out :: a -> Cont r b
fn :: a
out fn :: Cont r b
和fn = out fn,因此a ~ Cont r b。所以fn :: Cont r b。也就是说,fn 是一个延续,它被定义为应用到自身的out。
如果您不喜欢递归,可以改用fix。
let fn = fix out in return fn
在 Haskell 中,一般来说,但是尤其对于像 Cont 这样的高级 monad,你必须稍微相信你的类型。 Cont 将涉及很多像这样花哨的递归恶作剧,因此能够通过其类型识别值(并相信它与其类型一致地工作)很重要。
[1]那么,最小定义价值。在这种情况下,我相信它确实是唯一的固定点,但在某些情况下可以有多个。有关更多信息,请参阅denotational semantics。