成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

示例:JavaScript中的后續傳遞風格

開發 前端
后續傳遞風格(continuation-passing style,CPS)最初在1970年代作為一種編程風格出現,在1980年代到1990年代期間,其作為高級編程語言的編譯器的一種中間表達形式開始嶄露頭角。本文介紹了CPS所扮演的兩種角色——作為JavaScript中的一種非阻塞編程風格,以及作為一種功能性語言的中間形式(簡要介紹)。

現在,CPS作為非阻塞式(通常是分布式的)系統的編程風格而被再次發掘出來。

我對CPS很有好感,因為它是我獲取博士學位的一個秘密武器。它十有八九幫我消減掉了一兩年的時間和一些難以估量的痛苦。

本文介紹了CPS所扮演的兩種角色——作為JavaScript中的一種非阻塞編程風格,以及作為一種功能性語言的中間形式(簡要介紹)。

內容包括:

◆JavaScript中的CPS

◆CPS用于Ajax編程

◆用在非阻塞式編程(node.js)中的CPS

◆CPS用于分布式編程

◆如何使用CPS來實現異常

◆極簡Lisp的一個CPS轉換器

◆如何用Lisp實現call/cc

◆如何用JavaScript實現call/cc

請往下閱讀以了解更多內容。

什么是持續傳送風格?

如果一種語言支持后續(continuation)的話,編程者就可以添加諸如異常、回溯、線程以及構造函數一類的控制構造。

可惜的是,許多關于后續的解釋(我的也包括在內)給人的感覺是含糊不清,令人難以滿意。

后續傳遞風格是那么的基礎。

后續傳遞風格賦予了后續在代碼方面的意義。

更妙的是,編程者可以自我發掘出后續傳遞風格來,如果其受限于下面這樣的一個約束的話:

沒有過程被允許返回到它的調用者中——永遠如此。

存在的一個啟示使得以這種風格編程成為可能:

過程可以在它們返回值時調用一個回調方法。

當一個過程(procedure)準備要“返回”到它的調用者中時,它在返回值時調用“當前后續(current continuation)”這一回調方法(由它的調用者提供)

一個后續是一個初始類型(first-class)返回點。

例子:標識函數

考慮這個正常寫法的標識函數:

  1. function id(x) {    
  2.          return x ;  

然后是后續傳遞風格的:

  1. function id(x,cc) {  
  2.    cc(x) ;  

有時候,把當前后續參數命名為ret會使得其目的更為明顯一些:

  1. function id(x,ret) {  
  2.    ret(x) ;  

例子:樸素階乘

下面是標準的樸素階乘:

  1. function fact(n) {  
  2.    if (n == 0)  
  3.      return 1 ;  
  4.    else 
  5.     return n * fact(n-1) ;  

下面是CPS風格實現的:

  1. function fact(n,ret) {  
  2.    if (n == 0)  
  3.      ret(1) ;  
  4.    else 
  5.     fact(n-1, function (t0) {  
  6.       ret(n * t0) }) ;  

接下來,為了“使用”這一函數,我們把一個回調方法傳給它:

  1. fact (5, function (n) {  
  2.     console.log(n) ; // 在Firebug中輸出120  
  3. })   

例子:尾遞歸階乘

下面是尾遞歸階乘:

  1. function fact(n) {  
  2.    return tail_fact(n,1) ;  
  3. }    
  4. function tail_fact(n,a) {  
  5.    if (n == 0)  
  6.      return a ;  
  7.    else 
  8.      return tail_fact(n-1,n*a) ;  

然后,是CPS實現方式的:

  1. function fact(n,ret) {  
  2.    tail_fact(n,1,ret) ;  
  3. }     
  4. function tail_fact(n,a,ret) {  
  5.    if (n == 0)  
  6.      ret(a) ;  
  7.    else 
  8.      tail_fact(n-1,n*a,ret) ;  
  9. }    

CPS和Ajax

Ajax是一種web編程技術,其使用JavaScript中的一個XMLHttpRequest對象來從服務器端(異步地)提取數據。(提取的數據不必是XML格式的。)CPS提供了一種優雅地實現Ajax編程的方式。使用XMLHttpRequest,我們可以寫出一個阻塞式的過程fetch(url),該過程抓取某個url上的內容,然后把內容作為串返回。這一方法的問題是,JavaScript是一種單線程語言,當JavaScript阻塞時,瀏覽器就被暫時凍結,不能動彈了。這會造成不愉快的用戶體驗。一種更好的做法是這樣的一個過程fetch(url, callback),其允許執行(或是瀏覽器呈現工作)的繼續,并且一旦請求完成就調用所提供的回調方法。在這種做法中,部分CPS轉換變成了一種自然的編碼方式。

實現fetch

實現fetch過程并不難,至于其以非阻塞模式或是阻塞模式操作則取決于編程者是否提供回調方法:

  1. /*  
  2.  對于客戶端—>服務器端的請求來說,  
  3.  fetch是一個可選阻塞的過程。  
  4.    
  5.  只有在給出url的情況下,過程才會阻塞并返回該url上的內容。  
  6.    
  7.  如果提供了onSuccess回調方法,  
  8.  則過程是非阻塞的,并使用文件的  
  9.  內容來調用回調方法。  
  10.    
  11.  如果onFail回調方法也提供了的話,  
  12.  則過程在失敗事件出現時調用onFail。  
  13.    
  14. */ 
  15.    
  16. function fetch (url, onSuccess, onFail) {  
  17.      // 只有在定義回調方法的情況下才是異步的  
  18.     var async = onSuccess ? true : false ;   // (別抱怨此行代碼的效率低下,    
  19.                                                                                   
  20.    // 否則你就是不明白關鍵所在。)  
  21.      var req ; // XMLHttpRequest對象.  
  22.    
  23. // XMLHttpRequest的回調方法:  
  24.    function processReqChange() {  
  25.      if (req.readyState == 4) {  
  26.        if (req.status == 200) {  
  27.          if (onSuccess)  
  28.             onSuccess(req.responseText, url, req) ;  
  29.         } else {  
  30.          if (onFail)  
  31.             onFail(url, req) ;  
  32.        }  
  33.      }  
  34.    }  
  35.    
  36. // 創建XMLHttpRequest對象:  
  37.    if (window.XMLHttpRequest)  
  38.       req = new XMLHttpRequest();  
  39.    else if (window.ActiveXObject)  
  40.       req = new ActiveXObject("Microsoft.XMLHTTP");  
  41.    
  42. // 如果是異步的話,設定回調方法:  
  43.    if (async)  
  44.       req.onreadystatechange = processReqChange;  
  45.    
  46. // 發起請求:  
  47.    req.open("GET", url, async);  
  48.    req.send(null);  
  49.    
  50. // 如果是異步的話,  
  51. // 返回請求對象,否則  
  52. //  返回響應.  
  53.    if (async)  
  54.       return req ;  
  55.    else 
  56.     return req.responseText ;  

例子:提取數據

考慮一個程序,該程序需要從UID中抓取一個名字

下面的兩種做法都要用到fetch:

  1. // 阻塞直到請求完成:  
  2.  var someName = fetch("./1031/name") ;  
  3.    
  4.  document.write ("someName: " + someName + "  
  5. ") ; 

 

  1. //不做阻塞的:  
  2.  fetch("./1030/name"function (name) {  
  3.   document.getElementById("name").innerHTML = name ;  
  4.  }) ;  
  5.   

CPS和非阻塞式編程

node.js是一個高性能的JavaScript服務器端平臺,在該平臺上阻塞式過程是不允許的。

巧妙的是,通常會阻塞的過程(比如網絡或是文件I/O)利用了通過結果來調用的回調方法。

對程序做部分CPS轉換促成了自然而然的node.js編程。

#p#

例子:簡單的web服務器

node.js中的一個簡單的web服務器把一個后續傳遞給文件讀取過程。相比于非阻塞式IO的基于select的方法,CPS使非阻塞I/O變得更加的簡單明了。

  1. var sys = require('sys') ;  
  2. var http = require('http') ;  
  3. var url = require('url') ;  
  4. var fs = require('fs') ;  
  5.    
  6. // Web服務器的根目錄:  
  7.  var DocRoot = "./www/" ;  
  8.    
  9. // 使用一個處理程序回調來創建web服務器:  
  10.  var httpd = http.createServer(function (req, res) {  
  11.    sys.puts(" request: " + req.url) ;  
  12.           
  13.          // 解析url:  
  14.    var u = url.parse(req.url,true) ;  
  15.    var path = u.pathname.split("/") ;  
  16.    
  17.          // 去掉路徑中的..:  
  18.    var localPath = u.pathname ;  
  19.    //  "  
  20. /.." => ""  
  21.    var localPath =  
  22.         localPath.replace(/[^/]+\/+[.][.]/g,"") ;  
  23.    //  ".." => "."  
  24.    var localPath = DocRoot +  
  25.                      localPath.replace(/[.][.]/g,".") ;  
  26.    
  27.    // 讀入被請求的文件,并把它發送回去.  
  28.    // 注:readFile用到了當前后續(current continuation):  
  29.    fs.readFile(localPath, function (err,data) {  
  30.      var headers = {} ;  
  31.    
  32.      if (err) {  
  33.        headers["Content-Type"] = "text/plain" ;  
  34.        res.writeHead(404, headers);  
  35.        res.write("404 File Not Found\n") ;  
  36.        res.end() ;  
  37.      } else {  
  38.        var mimetype = MIMEType(u.pathname) ;  
  39.    
  40.     // 如果沒有找出內容類型的話,  
  41.      // 就由客戶來猜測.  
  42.        if (mimetype)  
  43.          headers["Content-Type"] = mimetype ;  
  44.          res.writeHead(200, headers) ;  
  45.         
  46.             res.write(data) ;  
  47.        res.end() ;  
  48.         }  
  49.     }) ;  
  50.  }) ;  
  51.    
  52. // 映射后綴名和MIME類型:  
  53.  var MIMETypes = {  
  54.   "html" : "text/html" ,  
  55.   "js"   : "text/javascript" ,  
  56.   "css"  : "text/css" ,  
  57.   "txt"  : "text/plain" 
  58. } ;  
  59.    
  60. function MIMEType(filename) {  
  61.   var parsed = filename.match(/[.](.*)$/) ;  
  62.   if (!parsed)  
  63.     return false ;  
  64.   var ext = parsed[1] ;  
  65.   return MIMEType[ext] ; }  
  66.    
  67. // 啟動服務器,監聽端口8000:  
  68.  httpd.listen(8000) ; 

CPS用于分布式計算

CPS簡化了把計算分解成本地部分和分布部分的做法。

假設你編寫了一個組合的choose函數;開始是一種正常的方式:

  1. function choose (n,k) {  
  2.  return       fact(n) /  
  3.           (fact(k) * fact(n-k)) ;  
  4.  } 

現在,假設你想要在服務器端而不是本地計算階乘。

你可以重新把fact寫成阻塞的并等待服務器端的響應。

那樣的做法很糟糕。

相反,假設你使用CPS來寫choose的話:

  1. function choose(n,k,ret) {  
  2.    fact (n,   function (factn) {  
  3.    fact (n-k, function (factnk) {  
  4.    fact (k,   function (factk) {  
  5.    ret  (factn / (factnk * factk)) }) }) })  
  6.   } 

現在,重新把fact定義成在服務器端的異步計算階乘就是一件很簡單的事情了。

(有趣的練習:修改node.js服務器端以讓這一做法生效。)

使用CPS來實現異常

一旦程序以CPS風格實現,其就破壞了語言中的普通的異常機制。 幸運的是,使用CPS來實現異常是一件很容易的事情。

異常是后續的一種特例。

通過把當前異常后續(current exceptional continuation)與當前后續一起做傳遞,你可以實現對try/catch代碼塊的脫糖處理。

考慮下面的例子,該例子使用異常來定義階乘的一個“完全”版本:

  1. function fact (n) {  
  2.    if (n < 0)  
  3.      throw "n < 0" ;  
  4.    else if (n == 0)  
  5.      return 1 ;  
  6.    else 
  7.      return n * fact(n-1) ; }  
  8.    
  9. function total_fact (n) {  
  10.    try {  
  11.      return fact(n) ;  
  12.    } catch (ex) {  
  13.      return false ;    
  14.    }  
  15. }  
  16.    
  17. document.write("total_fact(10): " + total_fact(10)) ;  
  18. document.write("total_fact(-1): " + total_fact(-1)) ; 

通過使用CPS來添加異常后續,我們就可以對throw、try和catch做脫糖處理:

  1. function fact (n,ret,thro) {  
  2.   if (n < 0)  
  3.     thro("n < 0")  
  4.    else if (n == 0)  
  5.     ret(1)  
  6.   else 
  7.     fact(n-1,  
  8.          function (t0) {  
  9.            ret(n*t0) ;  
  10.          },  
  11.          thro)  
  12.  }  
  13.    
  14. function total_fact (n,ret) {  
  15.    fact (n,ret,  
  16.      function (ex) {  
  17.        ret(false) ;  
  18.      }) ;  
  19.  }  
  20.    
  21. total_fact(10, function (res) {  
  22.    document.write("total_fact(10): " + res)  
  23. }) ;  
  24.    
  25. total_fact(-1, function (res) {  
  26.    document.write("total_fact(-1): " + res)  
  27. }) ; 

CPS用于編譯

三十年以來,CPS已經成為了功能性編程語言的編譯器的一種強大的中間表達形式。

CPS脫糖處理了函數的返回、異常和初始類型后續;函數調用變成了單條的跳轉指令。

換句話說,CPS在編譯方面做了許多繁重的提升工作。

把lambda演算轉寫成CPS

lambda演算是Lisp的一個縮影,只需足夠的表達式(應用程序、匿名函數和變量引用)來使得其對于計算是通用的。

  1. exp ::= (exp exp)           ; 函數應用  
  2.       |  (lambda (var) exp)  ; 匿名函數  
  3.       |  var              ; 變量引用 

下面的Racket代碼把這一語言轉換成CPS:

  1. (define (cps-convert term cont)  
  2.   (match term  
  3.     [`(,f ,e)  
  4.      ; =>  
  5.      (let (($f (gensym 'f))  
  6.            ($e (gensym 'e)))  
  7.        (cps-convert f `(lambda (,$f)  
  8.          ,(cps-convert e `(lambda (,$e)  
  9.              (,$f ,$e ,cont))))))]  
  10.      
  11.     [`(lambda (,v) ,e)  
  12.      ; =>  
  13.      (let (($k (gensym 'k)))  
  14.        `(,cont (lambda (,v ,$k)  
  15.                  ,(cps-convert e $k))))]  
  16.      
  17.     [(? symbol?)  
  18.      ; =>  
  19.      `(,cont ,term)]))  
  20.    
  21. (define (cps-convert-program term)  
  22.   (cps-convert term '(lambda (ans) ans))) 

對于感興趣的讀者來說,Olivier Danvy有許多關于編寫有效的CPS轉換器的文章。

使用Lisp實現call/cc

原語call-with-current-continuation(通常稱作call/cc)是現代編程中最強大的控制流結構。

CPS使得call/cc的實現成為了小菜一碟;這是一種語法上的脫糖:

  1. call/cc => (lambda (f cc) (f (lambda (x k) (cc x)) cc)) 

這一脫糖處理(與CPS轉換相結合)是準確理解call/cc所做工作的最好方式。

其所實現的正是其名稱所說明的:其使用一個已經捕捉了當前后續的過程來調用被作為參數指定的過程。

當捕捉了后續的過程被調用時,其把計算“返回”給計算創建點。

使用JavaScript實現call/cc

如果有人要把JavaScript中的代碼轉寫成后續傳遞風格的話,call/cc有一個很簡單的定義:
 

  1. function callcc (f,cc) {  
  2.   f(function(x,k) { cc(x) },cc)  

原文鏈接:http://article.yeeyan.org/view/213582/179432

【編輯推薦】

  1. 如何編寫高質量的JavaScript代碼
  2. 深入理解JavaScript的閉包特性
  3. 淺析JavaScript繼承方式
  4. 淺析JavaScript的寫類方式
  5. JavaScript跨域總結與解決辦法
責任編輯:陳貽新 來源: 譯言網
相關推薦

2017-05-27 15:21:38

JavaScript機器學習示例

2017-07-10 14:58:23

JavaScript代碼風格寫作準則

2017-01-12 14:55:50

JavaScript編程

2019-04-17 13:48:19

JavaScript前端傳遞

2018-12-03 17:15:47

JavaScripthtml前端

2009-06-09 21:54:26

傳遞參數JavaScript

2017-12-05 11:25:09

2023-08-14 14:04:14

JavaScript函數式編程

2009-07-01 14:31:01

JavaScript異

2011-02-24 14:57:15

ProFTPD

2020-07-26 00:40:48

JavaScript開發代碼

2024-07-01 12:09:12

2023-08-27 16:19:09

JavaScript編程語言

2022-08-29 08:33:22

SocatLinux命令

2010-09-17 09:35:51

SQL中if語句

2012-02-21 14:04:15

Java

2025-02-12 10:51:51

2014-01-03 09:13:39

JavaScriptthis

2013-05-08 10:36:07

JavaScriptJS詳解JavaScrip

2024-08-13 15:09:41

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲网站观看 | 国产a视频 | 久久久久av| 日本亚洲欧美 | 午夜激情免费视频 | 国产精品国产精品国产专区不卡 | 欧美精品一区二区在线观看 | 中日韩av| 精品国产91 | 成人在线精品视频 | 精品一区二区三区在线观看 | 欧美1页 | 日本精品视频一区二区三区四区 | 久久精品国产久精国产 | 日韩午夜网站 | 欧美日本一区二区 | 中文字幕日韩欧美一区二区三区 | 日本免费在线看 | 日韩不卡一区二区三区 | 日本精品久久久久 | 美女视频一区 | 亚洲va国产日韩欧美精品色婷婷 | 在线播放国产一区二区三区 | 天天操操 | www.一区二区三区 | 亚洲视频三区 | 欧美午夜精品理论片a级按摩 | 一区二区三区亚洲视频 | 久在线视频播放免费视频 | 色综久久 | 一区二区免费视频 | 欧美日韩国产一区二区三区 | 免费观看一级视频 | 日韩在线 | 91av在线免费| 一本一道久久a久久精品综合蜜臀 | 国产性网 | 国产精品欧美一区二区 | 成人h视频| 日韩精品一区二 | 日韩欧美在线一区 |