常常会遇到有人将Ruby的区块(Blocks)看作相当于JavaScript的“firstclassfunctions”的误解。由于传递功能,尤其是当你可以创建匿名的传递功能,这是非常强大的。事实上,JavaScript和Ruby有一个机制使其自然会认为等值。
人们在谈到为什么Ruby的区块不同于Python的函数时,通常会讲到一些关于Ruby和JavaScript的匿名分享,但Python没有。初看之下,一个Ruby区块就是一个“匿名函数”(或俗称一个“封装”),正如JavaScript函数就是其中之一。
作为一个早期的Ruby/JavaScript开发者,无可否认我也有过这样的观点分享。错过一个重要的细节,对结果会产生较大影响。这个原理常被称为“Tennent’sCorrespondencePrinciple”,这条原理说:“Foragivenexpressionexpr,lambdaexprshouldbeequivalent.”这就是被称为抽象的原则,因为这意味着,用“区块”的方法很容易重构通用代码。例如,常见文件资源管理的情况。试想在Ruby中,File.open块形式是不存在的,你会看到以下代码:
begin f=File.open(filename,"r") #dosomethingwithf ensure f.close end |
在一般情况下,“区块”代码有着相同的开始和结束编码、不同的内部编码。现在重构这段代码,你会这样写:
defread_file(filename) f=File.open(filename,"r") yieldf ensure f.close end |
代码中的模式与重构实例:
read_file(filename)do|f| #dosomethingwithf End |
重要的是重构之后区块内的代码和以前一样。在以下情况时,我们可以重申抽象原则的对应原理:
#dosomethingwithf |
应相等于:
do #dosomethingwith end |
乍一看,在Ruby和JavaScript中确实如此。例如,假设你正在使用的文件打印它的mtime。您可以轻松地重构相当于在JavaScript:
try{ //imaginaryJSfileAPI varf=File.open(filename,"r"); sys.print(f.mtime); }finally{ f.close(); } |
到这里:
read_file(function(f){ sys.print(f.mtime); }); |
事实上,这样的情况往往给人错误的印象,Ruby和JavaScript有同样用匿名函数重构常用功能的能力。
不过,再来一个稍微复杂一些的例子。我们首先在Ruby中编写一个简单的类,计算文件的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 |
同样地,需要注意的重点是,我们构建区块却并不改变它的内部代码。但不幸的是,这个相同情况的例子无法在JavaScript中正常工作。让我们在JavaScript中来写等同的FileInfo类:
//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);
}
}; |
如果我们试图将其转换成一个接受重复函数的代码,那mtime方法看起来将是:在这里有两个非常普遍的问题。首先是上下文改变了。我们可以通过允许绑定第二参数,但这意味着每次重构时需要确认并通过一个变量传递参数,就是说这一情况会在因为缺乏JavaScript信任组件时而出现。
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); }); } |
这很烦人,更棘手的还在于,它是从内部返回结果而不是从函数外部。这个真实的结果违反了抽象原则中的对应原理。相反,在函数中用区块方法毫不费力地重构具有相同开始和结束的代码时,JavaScript库作者需要考虑使用者对API处理嵌套函数时进行的一些操作。作为一个JavaScript库资源的编写者和使用者看来,这提供了一个很好的基于区块的API。
迭代和回调
值得注意的是,区块lambda函数接受功能调用的案例包括迭代器、同步与互斥、资源管理(如区块形式的File.open)。
使用函数作为回调时,关键字不再有意义。从一个已经返回的函数返回是什么意思?在这种情况下,通常涉及回调函数lambda表达式做出了很大的意义。在我看来,这解释了为什么JavaScript事件触发代码,涉及了大量的回调。
由于这些问题,ECMA工作组负责的ECMAScript,TC39,正在考虑加入块lambda表达式语言。这将意味着,上面的例子可重构:
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并没有实质性改变这个例子,并且要注意区块lambda表达式自动返回他们的最后一个语句。
经验显示,Smalltalk和Ruby不需要理解一种语言可怕的对应原理,满足它获得自己想要的结果。“迭代”概念并不内置到语言,而是被自然块定义的结果。这使得Ruby以及其他常用语言的开发者可建立自定义的丰富、内置的迭代设置。作为一个JavaScript实践者,我经常碰到的情况是,用一个for循环比使用forEach更为简单明了。
业界人士观点
munificent:Inordertohavealanguagewithreturn(andpossiblysuperandothersimilarkeywords)thatsatisfiesthecorrespondenceprinciple,thelanguagemust,likeRubyandSmalltalkbeforeit,haveafunctionlambdaandablocklambda.Keywordslikereturnalwaysreturnfromthefunctionlambda,eveninsideofblocklambdasnestedinside.Incaseyouwanttogetyourgoogle/wikipediaon,whatKatzistalkingabouthereisa"non-localreturn".
更多评论详细请点击这里>>
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网站地图 百度网站地图 网站地图
公司地址:大连市沙河口区中山路692号辰熙星海国际2317 客服电话:0411-39943997 QQ:2088827823 37482752
法律声明:未经许可,任何模仿本站模板、转载本站内容等行为者,本站保留追究其法律责任的权利! 隐私权政策声明