理解call/cc

第一次见call/cc,顿时懵逼。百思不得其解。

后来想明白了,发现大多数网上的讲解有误。属于生拿结果套解法,看起来没有错,但是顺着这个错误的思路,就死活理解不了。

比如以下的程序:

(+ 
  (*
    (call/cc
      (lambda (c)
        (+ 
          (c 2) 
          3))) 
    5) 
  8)

一般网上的讲解是这样的,call/cc内部的lambda参数c一旦调用,就忽略掉下一条语句(+ 3),直接返回到call/cc处,所以等价于代码

(+ 
  (* 2 5)
  8)

得出18。

看起来一切都很美好,代码跑起来也符合18的结果。但是,让你接着理解,使用call/cc,就懵逼了吧。因为跟着错误的思路,你根本无法理解它的用处。

比如 (set! *this* c),这是什么鬼。

以下是正确理解的思路:

call/cc表达式,提取当前位置的延续。

那么,上一个例子,可以先简化为

(+ 
  (* 
    (call/cc …)
    5)
  8)

什么叫当前位置的延续呢?

比如

(+ 
  (* x 5) 
  8)

对于x来说,它的延续就是它将要被做的事。这里就是,*5然后+8。

这个延续如何用代码表示呢?

(lambda (k)
  (+ 
    (* k 5) 
    8))

那么对于x 也就是(call/cc …)的延续就是

(lambda (k)
  (+ 
    (* k 5) 
    8))

现在来看(call/cc ...)语句内部,当call/cc取得当前位置的延续后,它需要一个回调函数来使用这个延续。对吧?因为从这里控制流开始向回调函数处跳转,而不是返回到(call/cc ...)所在的当前栈。

也就是说,(+ (* (call/cc …)5) 8) 的加法和乘法是永远也不会执行的!从call/cc开始,就走了,走了,永远也不回来了。去哪了,去(call/cc f)的回调函数f那里了。

这就是网上通常解释的错误之处,解释成(call/cc ...)表达式的结果,去执行(+ ( x 5) 8)了。因为这里有一个陷阱,(call/cc ...)在这里太容易看成是求值结果得到x,然后值x被带入(+ ( x 5) 8)求值了。因为这是call/cc于scheme例程所格格不入的地方。

然后来看回调函数f:

(lambda (c)
  (+ 
    (c 2)
    3))

f被以call/cc得到的延续为参数调用,同时这个延续也作为f的回调函数,请注意后一点。

参数c在这里带入

(lambda (k)
  (+ 
    (* k 5) 
    8))

那么回调函数f在这里的求值就是:

((lambda (c)
   (+ 
     (c 2) 
     3))
 (lambda (k)
   (+ 
     (* k 5) 
     8)))

注意 这里的求值是永远不会被返回的。(普通scheme程序这里会得到21)

因为回调函数f,在求值时将控制流传递给了它的回调函数c,如果使用有return的语言来表达的话。

var f = function(c){
  var x = c(2);
  return x;
  x+3;
}

x+3是永远不会执行的。

这样的返回方式是call/cc的特殊之处,正是这样,它才能暴露出整个程序的控制流。

所以整个程序的求值返回了

((lambda (c)
   (c 2))
 (lambda (k)
   (+ 
     (* k 5) 
     8)))

的结果。

注意这个结果和(+ (* x 5) 8)这个外层函数一点关系都没有,它已经成为过去了。

那么现在我们就很好理解

(set! *this* c)

(*this* 10)

这样的写法了。延续c只不过被储存了起来,随时调用罢了。而这个c在当下与过去的外层函数一点关系没有了。

(+ 
  (*
    (call/cc
      (lambda (c)
        (set! *this* c) 
        1)) 
    5) 
  8)

在这个例子里,因为回调函数也就是延续c一直没有被调用。

那么call/cc的回调函数会返回1,结果是13。

但这里我们把延续存起来了,于是:

(*this* 2)

相当于

((lambda (c)
   (c 2))
 (lambda (k)
   (+ 
     (* k 5) 
     8)))

得到 18

(*this* 3)

相当于

((lambda (c)
   (c 3))
 (lambda (k)
   (+ 
     (* k 5) 
     8)))

得到 23

可以看到这里的 this 等于c 和原函数 (+ (* x 5) 8) 一点关系没有了。

© 2019-2020 guenchi.  All rights reserved.

results matching ""

    No results matching ""