示例:JavaScript中的后续传递风格(1)(2)
例子:简单的web服务器
node.js中的一个简单的web服务器把一个后续传递给文件读取过程。相比于非阻塞式IO的基于select的方法,CPS使非阻塞I/O变得更加的简单明了。
- var sys = require('sys') ;
- var http = require('http') ;
- var url = require('url') ;
- var fs = require('fs') ;
- // Web服务器的根目录:
- var DocRoot = "./www/" ;
- // 使用一个处理程序回调来创建web服务器:
- var httpd = http.createServer(function (req, res) {
- sys.puts(" request: " + req.url) ;
- // 解析url:
- var u = url.parse(req.url,true) ;
- var path = u.pathname.split("/") ;
- // 去掉路径中的..:
- var localPath = u.pathname ;
- // "
- /.." => ""
- var localPath =
- localPath.replace(/[^/]+/+[.][.]/g,"") ;
- // ".." => "."
- var localPath = DocRoot +
- localPath.replace(/[.][.]/g,".") ;
- // 读入被请求的文件,并把它发送回去.
- // 注:readFile用到了当前后续(current continuation):
- fs.readFile(localPath, function (err,data) {
- var headers = {} ;
- if (err) {
- headers["Content-Type"] = "text/plain" ;
- res.writeHead(404, headers);
- res.write("404 File Not Foundn") ;
- res.end() ;
- } else {
- var mimetype = MIMEType(u.pathname) ;
- // 如果没有找出内容类型的话,
- // 就由客户来猜测.
- if (mimetype)
- headers["Content-Type"] = mimetype ;
- res.writeHead(200, headers) ;
- res.write(data) ;
- res.end() ;
- }
- }) ;
- }) ;
- // 映射后缀名和MIME类型:
- var MIMETypes = {
- "html" : "text/html" ,
- "js" : "text/javascript" ,
- "css" : "text/css" ,
- "txt" : "text/plain"
- } ;
- function MIMEType(filename) {
- var parsed = filename.match(/[.](.*)$/) ;
- if (!parsed)
- return false ;
- var ext = parsed[1] ;
- return MIMEType[ext] ; }
- // 启动服务器,监听端口8000:
- httpd.listen(8000) ;
CPS用于分布式计算
CPS简化了把计算分解成本地部分和分布部分的做法。
假设你编写了一个组合的choose函数;开始是一种正常的方式:
- function choose (n,k) {
- return fact(n) /
- (fact(k) * fact(n-k)) ;
- }
现在,假设你想要在服务器端而不是本地计算阶乘。
你可以重新把fact写成阻塞的并等待服务器端的响应。
那样的做法很糟糕。
相反,假设你使用CPS来写choose的话:
- function choose(n,k,ret) {
- fact (n, function (factn) {
- fact (n-k, function (factnk) {
- fact (k, function (factk) {
- ret (factn / (factnk * factk)) }) }) })
- }
现在,重新把fact定义成在服务器端的异步计算阶乘就是一件很简单的事情了。
(有趣的练习:修改node.js服务器端以让这一做法生效。)
使用CPS来实现异常
一旦程序以CPS风格实现,其就破坏了语言中的普通的异常机制。 幸运的是,使用CPS来实现异常是一件很容易的事情。
异常是后续的一种特例。
通过把当前异常后续(current exceptional continuation)与当前后续一起做传递,你可以实现对try/catch代码块的脱糖处理。
考虑下面的例子,该例子使用异常来定义阶乘的一个“完全”版本:
- function fact (n) {
- if (n < 0)
- throw "n < 0" ;
- else if (n == 0)
- return 1 ;
- else
- return n * fact(n-1) ; }
- function total_fact (n) {
- try {
- return fact(n) ;
- } catch (ex) {
- return false ;
- }
- }
- document.write("total_fact(10): " + total_fact(10)) ;
- document.write("total_fact(-1): " + total_fact(-1)) ;
通过使用CPS来添加异常后续,我们就可以对throw、try和catch做脱糖处理:
- function fact (n,ret,thro) {
- if (n < 0)
- thro("n < 0")
- else if (n == 0)
- ret(1)
- else
- fact(n-1,
- function (t0) {
- ret(n*t0) ;
- },
- thro)
- }
- function total_fact (n,ret) {
- fact (n,ret,
- function (ex) {
- ret(false) ;
- }) ;
- }
- total_fact(10, function (res) {
- document.write("total_fact(10): " + res)
- }) ;
- total_fact(-1, function (res) {
- document.write("total_fact(-1): " + res)
- }) ;
CPS用于编译
三十年以来,CPS已经成为了功能性编程语言的编译器的一种强大的中间表达形式。
CPS脱糖处理了函数的返回、异常和初始类型后续;函数调用变成了单条的跳转指令。
换句话说,CPS在编译方面做了许多繁重的提升工作。
把lambda演算转写成CPS
lambda演算是Lisp的一个缩影,只需足够的表达式(应用程序、匿名函数和变量引用)来使得其对于计算是通用的。
- exp ::= (exp exp) ; 函数应用
- | (lambda (var) exp) ; 匿名函数
- | var ; 变量引用
下面的Racket代码把这一语言转换成CPS:
- (define (cps-convert term cont)
- (match term
- [`(,f ,e)
- ; =>
- (let (($f (gensym 'f))
- ($e (gensym 'e)))
- (cps-convert f `(lambda (,$f)
- ,(cps-convert e `(lambda (,$e)
- (,$f ,$e ,cont))))))]
- [`(lambda (,v) ,e)
- ; =>
- (let (($k (gensym 'k)))
- `(,cont (lambda (,v ,$k)
- ,(cps-convert e $k))))]
- [(? symbol?)
- ; =>
- `(,cont ,term)]))
- (define (cps-convert-program term)
- (cps-convert term '(lambda (ans) ans)))
对于感兴趣的读者来说,Olivier Danvy有许多关于编写有效的CPS转换器的文章。
使用Lisp实现call/cc
原语call-with-current-continuation(通常称作call/cc)是现代编程中最强大的控制流结构。
CPS使得call/cc的实现成为了小菜一碟;这是一种语法上的脱糖:
- call/cc => (lambda (f cc) (f (lambda (x k) (cc x)) cc))
这一脱糖处理(与CPS转换相结合)是准确理解call/cc所做工作的最好方式。
其所实现的正是其名称所说明的:其使用一个已经捕捉了当前后续的过程来调用被作为参数指定的过程。
当捕捉了后续的过程被调用时,其把计算“返回”给计算创建点。
使用JavaScript实现call/cc
如果有人要把JavaScript中的代码转写成后续传递风格的话,call/cc有一个很简单的定义:
- function callcc (f,cc) {
- f(function(x,k) { cc(x) },cc)
- }
原文链接:http://article.yeeyan.org/view/213582/179432






