1 Preface

后继传递风格(continuation-passing style CPS wiki) 具体解释请阅读wiki。

CPS 最初在1970年代作为一种编程风格出现,主要用于函数式编程; 在1980年代到1990年代期间,其作为高级编程语言的编译器的一种中间表达形式开始崭露头角。

现在,CPS作为非阻塞系统(通常是分布式)的一种编程风格被再次发掘出来。

1.1 CPS in Haskell

我们使用Haskell 实现勾股定理(Pythagorean theorem) 计算斜边的长度。 传统的实现如下:

1
2
3
4
5
6
7
8
square :: Double -> Double
square x = x * x

add :: Double -> Double -> Double
add x y = x + y

pythagorean :: Double -> Double -> Double
pythagorean x y = sqrt (add (square x)  (square y))

然后我们将其改造成 CPS 的方式,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
add_cps :: Double -> Double -> ((Double -> r) -> r)
add_cps x y = \k -> k (add x y)

square_cps :: Double -> ((Double -> r) -> r)
square_cps x = \k -> k (square x)

pythagoras_cps :: Double -> Double -> ((Double -> r) -> r)
pythagoras_cps x y = sqrt \k ->
    square_cps x $ \x_squared ->
    square_cps y $ \y_squared ->
    add_cps x_squared y_squared $ k

main = do
    pythagoras_cps 3 4 print

1.2 CPS用于非阻塞编程

Node.js 是用于JavaScript的高性能服务器端平台,其中禁止了阻塞过程。
因为NodeJs 底层是采用 libuv 做的支撑;

libuv 是一个纯 C 语言实现的 Reactor 库, 里面大量使用 CPS 。

优点: 我们可以很方便的将 callback 注册到 event_loop里面,当事件触发时,执行我们的callback。
也就是说,我们可以拿他来做事件驱动编程,异步并且高效。 由于是单线程,所有我们也可以减少lock 的使用。

提了这么多优点,当然也是有缺点的:

1 在callback 里面,我们不能使用有阻塞的逻辑(比如sleep, 同步读写文件等),否则后续的所有的 callback 的执行都会被推迟。
2 另一个就是:我们基于CPS 模式做了大量CPS, 当调用层级太深,会增加开发的理解和维护成本。

1.3 CPS in Go

我们写了一个例子:把一个匿名 func 作为参数,传入其他func 里面去执行;

我们也可以在这个匿名 func 外面再包装一层,形成一个新的匿名func;这样可以实现更复杂的CPS。

1
2
3
4
5
6
7
8
9
func funcCPS(n int, f func(int)) {
    // do something
    f(n)
    // do something
}

func main() {
    funcCPS(5, func(i int) { fmt.Printf("result: %v\n", i)})
}

2 Last

我们所掌握的自以为傲的很多知识或者技能,在可见的未来将会被更新或者淘汰, 也许是在明天,或许几年,谁知道呢。