常常會(huì )遇到有人將Ruby的區塊(Blocks)看作相當于JavaScript的“firstclassfunctions”的誤解。由于傳遞功能,尤其是當你可以創(chuàng )建匿名的傳遞功能,這是非常強大的。事實(shí)上,JavaScript和Ruby有一個(gè)機制使其自然會(huì )認為等值。
人們在談到為什么Ruby的區塊不同于Python的函數時(shí),通常會(huì )講到一些關(guān)于Ruby和JavaScript的匿名分享,但Python沒(méi)有。初看之下,一個(gè)Ruby區塊就是一個(gè)“匿名函數”(或俗稱(chēng)一個(gè)“封裝”),正如JavaScript函數就是其中之一。
作為一個(gè)早期的Ruby/JavaScript開(kāi)發(fā)者,無(wú)可否認我也有過(guò)這樣的觀(guān)點(diǎn)分享。錯過(guò)一個(gè)重要的細節,對結果會(huì )產(chǎn)生較大影響。這個(gè)原理常被稱(chēng)為“Tennent’sCorrespondencePrinciple”,這條原理說(shuō):“Foragivenexpressionexpr,lambdaexprshouldbeequivalent.”這就是被稱(chēng)為抽象的原則,因為這意味著(zhù),用“區塊”的方法很容易重構通用代碼。例如,常見(jiàn)文件資源管理的情況。試想在Ruby中,File.open塊形式是不存在的,你會(huì )看到以下代碼:
begin f=File.open(filename,"r") #dosomethingwithf ensure f.close end |
在一般情況下,“區塊”代碼有著(zhù)相同的開(kāi)始和結束編碼、不同的內部編碼?,F在重構這段代碼,你會(huì )這樣寫(xiě):
defread_file(filename) f=File.open(filename,"r") yieldf ensure f.close end |
代碼中的模式與重構實(shí)例:
read_file(filename)do|f| #dosomethingwithf End |
重要的是重構之后區塊內的代碼和以前一樣。在以下情況時(shí),我們可以重申抽象原則的對應原理:
#dosomethingwithf |
應相等于:
do #dosomethingwith end |
乍一看,在Ruby和JavaScript中確實(shí)如此。例如,假設你正在使用的文件打印它的mtime。您可以輕松地重構相當于在JavaScript:
try{ //imaginaryJSfileAPI varf=File.open(filename,"r"); sys.print(f.mtime); }finally{ f.close(); } |
到這里:
read_file(function(f){ sys.print(f.mtime); }); |
事實(shí)上,這樣的情況往往給人錯誤的印象,Ruby和JavaScript有同樣用匿名函數重構常用功能的能力。
不過(guò),再來(lái)一個(gè)稍微復雜一些的例子。我們首先在Ruby中編寫(xiě)一個(gè)簡(jiǎn)單的類(lèi),計算文件的mtime和檢索它的正文:
classFileInfo definitialize(filename) @name=filename end #calculatetheFile's+mtime+ defmtime f=File.open(@name,"r") mtime=mtime_for(f) return"tooold"ifmtime<(Time.now-1000) puts"recent!" mtime ensure f.close end #retrievethatfile's+body+ defbody f=File.open(@name,"r") f.read ensure f.close end #ahelpermethodtoretrievethemtimeofafile defmtime_for(f) File.mtime(f) end end |
我們可以用區塊很容易地重構這段代碼:
classFileInfo definitialize(filename) @name=filename end #refactorthecommonfilemanagementcodeintoamethod #thattakesablock defmtime with_filedo|f| mtime=mtime_for(f) return"tooold"ifmtime<(Time.now-1000) puts"recent!" mtime end end defbody with_file{|f|f.read} end defmtime_for(f) File.mtime(f) end private #thismethodopensafile,callsablockwithit,and #ensuresthatthefileisclosedoncetheblockhas #finishedexecuting. defwith_file f=File.open(@name,"r") yieldf ensure f.close end end |
同樣地,需要注意的重點(diǎn)是,我們構建區塊卻并不改變它的內部代碼。但不幸的是,這個(gè)相同情況的例子無(wú)法在JavaScript中正常工作。讓我們在JavaScript中來(lái)寫(xiě)等同的FileInfo類(lèi):
//constructorfortheFileInfoclass FileInfo=function(filename){ this.name=filename; }; FileInfo.prototype={ //retrievethefile'smtime mtime:function(){ try{ varf=File.open(this.name,"r"); varmtime=this.mtimeFor(f);
if(mtime
return"tooold";
}
sys.print(mtime);
}finally{
f.close();
}
},
//retrievethefile'sbody
body:function(){
try{
varf=File.open(this.name,"r");
returnf.read();
}finally{
f.close();
}
},
//ahelpermethodtoretrievethemtimeofafile
mtimeFor:function(f){
returnFile.mtime(f);
}
}; |
如果我們試圖將其轉換成一個(gè)接受重復函數的代碼,那mtime方法看起來(lái)將是:在這里有兩個(gè)非常普遍的問(wèn)題。首先是上下文改變了。我們可以通過(guò)允許綁定第二參數,但這意味著(zhù)每次重構時(shí)需要確認并通過(guò)一個(gè)變量傳遞參數,就是說(shuō)這一情況會(huì )在因為缺乏JavaScript信任組件時(shí)而出現。
function() { // refactor the common file management code into a method // that takes a block this.withFile(function(f) { var mtime = this.mtimeFor(f); if (mtime < new Date() - 1000) { return "too old"; } sys.print(mtime); }); } |
這很煩人,更棘手的還在于,它是從內部返回結果而不是從函數外部。這個(gè)真實(shí)的結果違反了抽象原則中的對應原理。相反,在函數中用區塊方法毫不費力地重構具有相同開(kāi)始和結束的代碼時(shí),JavaScript庫作者需要考慮使用者對API處理嵌套函數時(shí)進(jìn)行的一些操作。作為一個(gè)JavaScript庫資源的編寫(xiě)者和使用者看來(lái),這提供了一個(gè)很好的基于區塊的API。
迭代和回調
值得注意的是,區塊lambda函數接受功能調用的案例包括迭代器、同步與互斥、資源管理(如區塊形式的File.open)。
使用函數作為回調時(shí),關(guān)鍵字不再有意義。從一個(gè)已經(jīng)返回的函數返回是什么意思?在這種情況下,通常涉及回調函數lambda表達式做出了很大的意義。在我看來(lái),這解釋了為什么JavaScript事件觸發(fā)代碼,涉及了大量的回調。
由于這些問(wèn)題,ECMA工作組負責的ECMAScript,TC39,正在考慮加入塊lambda表達式語(yǔ)言。這將意味著(zhù),上面的例子可重構:
FileInfo=function(name){ this.name=name; }; FileInfo.prototype={ mtime:function(){ //usetheproposedblocksyntax,`{|args|}`. this.withFile{|f| //inblocklambdas,+this+isunchanged varmtime=this.mtimeFor(f);
if(mtime
//blocklambdasreturnfromtheirnearestfunction
return"tooold";
}
sys.print(mtime);
}
},
body:function(){
this.withFile{|f|f.read();}
},
mtimeFor:function(f){
returnFile.mtime(f);
},
withFile:function(block){
try{
varf=File.open(this.name,"r");
block(f);
}finally{
f.close();
}
}
}; |
TC39并沒(méi)有實(shí)質(zhì)性改變這個(gè)例子,并且要注意區塊lambda表達式自動(dòng)返回他們的最后一個(gè)語(yǔ)句。
經(jīng)驗顯示,Smalltalk和Ruby不需要理解一種語(yǔ)言可怕的對應原理,滿(mǎn)足它獲得自己想要的結果。“迭代”概念并不內置到語(yǔ)言,而是被自然塊定義的結果。這使得Ruby以及其他常用語(yǔ)言的開(kāi)發(fā)者可建立自定義的豐富、內置的迭代設置。作為一個(gè)JavaScript實(shí)踐者,我經(jīng)常碰到的情況是,用一個(gè)for循環(huán)比使用forEach更為簡(jiǎn)單明了。
業(yè)界人士觀(guān)點(diǎn)
munificent:Inordertohavealanguagewithreturn(andpossiblysuperandothersimilarkeywords)thatsatisfiesthecorrespondenceprinciple,thelanguagemust,likeRubyandSmalltalkbeforeit,haveafunctionlambdaandablocklambda.Keywordslikereturnalwaysreturnfromthefunctionlambda,eveninsideofblocklambdasnestedinside.Incaseyouwanttogetyourgoogle/wikipediaon,whatKatzistalkingabouthereisa"non-localreturn".
更多評論詳細請點(diǎn)擊這里>>
ericbb:Alternateformulationwithhypotheticalshift/reset(delimitedcontinuationsupport)andblocksthatreturnthesamewayfunctionsdo:
mtime:function(){ returnreset{ varmtime=shift(succeed){ this.withFile({|f| varmtime=this.mtimeFor(f);
if(mtime
return"tooold";
}
returnsucceed(mtime);
});
};
sys.print(mtime);
return"youngenough";
};
}, |
Thesucceedfunctionisafirstclass,indefinite-extentfunctionequivalentto:
function(mtime){
sys.print(mtime); return"youngenough"; } |
Copyright@ 2011-2016 版權所有:大連千億科技有限公司 遼ICP備11013762-3號 google網(wǎng)站地圖 百度網(wǎng)站地圖 網(wǎng)站地圖
公司地址:大連市沙河口區中山路692號辰熙星海國際2317 客服電話(huà):0411-39943997 QQ:2088827823 37482752
法律聲明:未經(jīng)許可,任何模仿本站模板、轉載本站內容等行為者,本站保留追究其法律責任的權利! 隱私權政策聲明