你的編程語言可以這樣做嗎?
更新時(shí)間:2006年09月07日 00:00:00 作者:
有一天,你在瀏覽自己的代碼,發(fā)現(xiàn)有兩大段代碼幾乎一樣。實(shí)際上,它們確實(shí)是一樣的——除了一個(gè)關(guān)于意大利面(Spaghetti)而另一個(gè)關(guān)于巧克力慕思(Chocolate Moose)。
// 一個(gè)小例子:
alert("偶要吃意大利面!");
alert("偶要吃巧克力慕思!");
嗯,這個(gè)例子碰巧是用javascript寫的,不過你就算不懂JavaScript,應(yīng)該也能明白它在干什么。
拷貝代碼不好。于是,你創(chuàng)建了個(gè)函數(shù)
function SwedishChef( food ){
alert("偶要吃" + food + "!");
}
SwedishChef("意大利面");
SwedishChef("巧克力慕思");
Ok,這只是一個(gè)很小很小的例子而已,相信你能想像到個(gè)更實(shí)際一點(diǎn)的例子。這段代碼有很多優(yōu)點(diǎn),你全都聽過幾萬次了:可維護(hù)性、可讀性、抽象性 = 好!
現(xiàn)在你留意到有另外兩段代碼幾乎跟它們一模一樣,除了一個(gè)反復(fù)調(diào)用一個(gè)叫BoomBoom的函數(shù),另一個(gè)反復(fù)調(diào)用一個(gè)叫PutInPot的。除此之外,這兩段代碼簡直沒什么兩樣:
alert("拿龍蝦");
PutInPot("龍蝦");
PutInPot("水");
alert("拿雞肉");
BoomBoom("雞肉");
BoomBoom("椰子醬");
現(xiàn)在要想個(gè)辦法,使得你可以將一個(gè)函數(shù)用作另一個(gè)函數(shù)的參數(shù)。這是個(gè)重要的能力,因?yàn)槟愀菀讓⒖蚣艽a寫成一個(gè)函數(shù)(emu注:還記得template method模式吧?)。
function Cook( i1, i2, f ){
alert("拿" + i1);
f(i1);
f(i2);
}
Cook( "龍蝦", "水", PutInPot );
Cook( "雞肉", "椰子醬", BoomBoom );
看看,我們居然把函數(shù)當(dāng)成調(diào)用參數(shù)傳遞了!
你的編程語言能辦到嗎?
等等……假如我們已經(jīng)有了PutInPot和BoomBoom這些函數(shù)的具體實(shí)現(xiàn)代碼(而且又不需要在別的地方重用它們),那么用內(nèi)聯(lián)語法把它們寫進(jìn)函數(shù)調(diào)用里面不是比顯式的聲明這兩個(gè)函數(shù)更漂亮嗎?
Cook( "龍蝦",
"水",
function(x) { alert("pot " + x); } );
Cook( "雞肉",
"椰子醬",
function(x) { alert("boom " + x); } );
耶,真方便!請(qǐng)注意我只是隨手創(chuàng)建了個(gè)函數(shù),甚至不用考慮怎么為它起名,只要拎著它的耳朵把它往一個(gè)函數(shù)里頭一丟就可以了。
當(dāng)你一想到作為參數(shù)的匿名函數(shù),你也許想到對(duì)那些對(duì)數(shù)組里的每個(gè)元素進(jìn)行相同操作的代碼。
var a = [1,2,3];
for (i=0; i<a.length; i++){
a[i] = a[i] * 2;
}
for (i=0; i<a.length; i++){
alert(a[i]);
}
常常要對(duì)數(shù)組里的所有元素做同一件事,因此你可以寫個(gè)這樣的函數(shù)來幫忙:
function map(fn, a){
for (i = 0; i < a.length; i++){
a[i] = fn(a[i]);
}
}
現(xiàn)在你可以把上面的東西改成:
map( function(x){return x*2;}, a );
map( alert, a );
另一個(gè)常見的任務(wù)是將數(shù)組內(nèi)的所有元素按照某總方式匯總起來:
function sum(a){
var s = 0;
for (i = 0; i < a.length; i++)
s += a[i];
return s;
}
function join(a){
var s = "";
for (i = 0; i < a.length; i++)
s += a[i];
return s;
}
alert(sum([1,2,3]));
alert(join(["a","b","c"]));
sum和join長得很像,你也許想把它們抽象為一個(gè)將數(shù)組內(nèi)的所有元素按某種算法匯總起來的泛型函數(shù):
function reduce(fn, a, init){
var s = init;
for (i = 0; i < a.length; i++)
s = fn( s, a[i] );
return s;
}
function sum(a){
return reduce( function(a, b){ return a + b; }, a, 0 );
}
function join(a){
return reduce( function(a, b){ return a + b; }, a, "" );
}
許多早期的編程語言沒法子做這種事。有些語言容許你做,卻又困難重重(例如C有函數(shù)指針,但你要在別處聲明和定義函數(shù))。面向?qū)ο笳Z言也不確保你用函數(shù)可以干些啥(把函數(shù)當(dāng)對(duì)象處理?)。
如果你想將函數(shù)視為一類對(duì)象,Java要求你建立一個(gè)有單方法的對(duì)象,稱為算子對(duì)象。許多面向?qū)ο笳Z言要你為每個(gè)類都建立一個(gè)完整文件,像這樣開發(fā)可真叫快。如果你的編程語言要你使用算子對(duì)象來包裝方法(而不是把方法本身當(dāng)成對(duì)象),你就不能徹底得到現(xiàn)代(動(dòng)態(tài))編程語言的好處。不妨試試看你可否退貨拿回些錢?
不用再寫那些除了經(jīng)過一個(gè)數(shù)組對(duì)每個(gè)元素做一些事情之外一無是處的函數(shù),有什么好處?
讓我們看回map函數(shù)。當(dāng)你要對(duì)數(shù)組內(nèi)的每個(gè)元素做一些事,你很可能不在乎哪個(gè)元素先做。無論由第一個(gè)元素開始執(zhí)行,還是是由最后一個(gè)元素執(zhí)行,你的結(jié)果都是一樣的,對(duì)不?如果你手頭上有2個(gè)CPU,你可以寫段代碼,使得它們各對(duì)一半的元素工作,于是乎map快了兩倍。
或者,發(fā)揮一下想像力,設(shè)想你在全球有千千萬萬臺(tái)服務(wù)器分布在全世界的若干個(gè)數(shù)據(jù)中心,你有一個(gè)真的很大很大的數(shù)組,嗯,再發(fā)揮一下想像力,設(shè)想這個(gè)數(shù)組記錄有整個(gè)互聯(lián)網(wǎng)的內(nèi)容。還了,現(xiàn)在你可以在幾千臺(tái)服務(wù)器上同時(shí)執(zhí)行map,讓每臺(tái)服務(wù)器都來解決同一個(gè)問題的一小部分。
那么在這個(gè)例子里面,編寫一段非常快的代碼來搜索整個(gè)互聯(lián)網(wǎng)這個(gè)問題,其實(shí)就和用一個(gè)簡單的字符串搜索器(算子)作為參數(shù)來調(diào)用map函數(shù)一樣簡單了。
希望你注意到一個(gè)真正有意思的要點(diǎn),如果你想要把map/reduce模式變成一個(gè)對(duì)所有人都有用,對(duì)所有人都能立刻派上用場的技術(shù),你只需要一個(gè)超級(jí)天才來寫最重要的一部分代碼,來讓map/reduce可以在一個(gè)巨大的并行計(jì)算機(jī)陣列上運(yùn)行,然后其他舊的但是一向在單一個(gè)循環(huán)中運(yùn)行良好的代碼,仍可以保持正確的運(yùn)行,惟一的差別只是比原來單機(jī)運(yùn)行快了n倍。這意味著它們都一不留神突然變成可以被用來解決一個(gè)巨大的問題的代碼。
讓我再啰嗦一下,通過把“循環(huán)”這個(gè)概念加以抽象,你可以把用任何你喜歡的方式來實(shí)現(xiàn)“循環(huán)”過程,包括可以實(shí)現(xiàn)讓循環(huán)迭代速度隨著硬件計(jì)算能力保持令人滿意的同步增長。
你現(xiàn)在應(yīng)該可以明白不久為何對(duì)那些對(duì)除了Java之外什么都沒被學(xué)過的計(jì)算機(jī)系學(xué)生表示不滿了:( http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html) :
Without understanding functional programming, you can't invent MapReduce, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their 6.001-equivalent programming class that purely functional programs have no side effects and are thus trivially parallelizable. The very fact that Google invented MapReduce, and Microsoft didn't, says something about why Microsoft is still playing catch up trying to get basic search features to work, while Google has moved on to the next problem: building Skynet^H^H^H^H^H^H the world's largest massively parallel supercomputer. I don't think Microsoft completely understands just how far behind they are on that wave.
不理解函數(shù)式編程,你就發(fā)明不了MapReduce這個(gè)讓Google的計(jì)算能力如此具有可擴(kuò)展性的算法。Map和Reduce這兩個(gè)術(shù)語源自Lisp語言和函數(shù)式編程……(這是另一篇文章的內(nèi)容,emu也不是很理解其中的各種說法的來龍去脈,就不翻譯了)
我希望你現(xiàn)在明白,把函數(shù)當(dāng)成基本類型的(動(dòng)態(tài))編程語言能讓你在編程過程中更好的進(jìn)行抽象化,也就是使代碼精悍、功能更內(nèi)聚、更具可重用性及更具有擴(kuò)展性。很多的Google應(yīng)用使用Map/Reduce模式,因此一有人對(duì)其優(yōu)化或修正缺陷,它們就都可以從中得益。
我準(zhǔn)備要再羅嗦一下,我認(rèn)為最有生產(chǎn)力的編程語言莫過于能讓你在不同層次上都可以進(jìn)行抽象化的。老掉牙的FORTRAN 語言以前是不讓你寫函數(shù)的注。C 有函數(shù)指針,可是它們都非常丑丑丑丑丑丑丑丑陋,不允許匿名聲明,又不能在用它們時(shí)實(shí)現(xiàn)它們而偏偏要放在別處去實(shí)現(xiàn)。Java讓你使用算子對(duì)象,一種更丑陋的東西。正如Steve Yegge所述,Java是個(gè)名詞王國 (http://steveyegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)。
作者注:這里提起了FORTRAN,不過我上次使用FORTRAN是27年前的事了。FORTRAN是有函數(shù)的,我碼字那會(huì)兒腦子里面想的大概是GW-BASIC語言。(emu注,basic確實(shí)只有所謂的子程序和go-sub語句,作用只是重新組織代碼結(jié)構(gòu)而已,沒有參數(shù)和調(diào)用堆棧,因此沒有真正的函數(shù)調(diào)用)
譯者注:原作者起了《你的編程語言可以這樣做嗎》這個(gè)標(biāo)題其實(shí)并不是這篇文章的真正價(jià)值所在,我轉(zhuǎn)這篇文章也不是因?yàn)樵髡呖梢园颜Z言的初級(jí)技巧玩得轉(zhuǎn),而是因?yàn)檫@是一篇map/reduce模型的示范。
// 一個(gè)小例子:
alert("偶要吃意大利面!");
alert("偶要吃巧克力慕思!");
嗯,這個(gè)例子碰巧是用javascript寫的,不過你就算不懂JavaScript,應(yīng)該也能明白它在干什么。
拷貝代碼不好。于是,你創(chuàng)建了個(gè)函數(shù)
function SwedishChef( food ){
alert("偶要吃" + food + "!");
}
SwedishChef("意大利面");
SwedishChef("巧克力慕思");
Ok,這只是一個(gè)很小很小的例子而已,相信你能想像到個(gè)更實(shí)際一點(diǎn)的例子。這段代碼有很多優(yōu)點(diǎn),你全都聽過幾萬次了:可維護(hù)性、可讀性、抽象性 = 好!
現(xiàn)在你留意到有另外兩段代碼幾乎跟它們一模一樣,除了一個(gè)反復(fù)調(diào)用一個(gè)叫BoomBoom的函數(shù),另一個(gè)反復(fù)調(diào)用一個(gè)叫PutInPot的。除此之外,這兩段代碼簡直沒什么兩樣:
alert("拿龍蝦");
PutInPot("龍蝦");
PutInPot("水");
alert("拿雞肉");
BoomBoom("雞肉");
BoomBoom("椰子醬");
現(xiàn)在要想個(gè)辦法,使得你可以將一個(gè)函數(shù)用作另一個(gè)函數(shù)的參數(shù)。這是個(gè)重要的能力,因?yàn)槟愀菀讓⒖蚣艽a寫成一個(gè)函數(shù)(emu注:還記得template method模式吧?)。
function Cook( i1, i2, f ){
alert("拿" + i1);
f(i1);
f(i2);
}
Cook( "龍蝦", "水", PutInPot );
Cook( "雞肉", "椰子醬", BoomBoom );
看看,我們居然把函數(shù)當(dāng)成調(diào)用參數(shù)傳遞了!
你的編程語言能辦到嗎?
等等……假如我們已經(jīng)有了PutInPot和BoomBoom這些函數(shù)的具體實(shí)現(xiàn)代碼(而且又不需要在別的地方重用它們),那么用內(nèi)聯(lián)語法把它們寫進(jìn)函數(shù)調(diào)用里面不是比顯式的聲明這兩個(gè)函數(shù)更漂亮嗎?
Cook( "龍蝦",
"水",
function(x) { alert("pot " + x); } );
Cook( "雞肉",
"椰子醬",
function(x) { alert("boom " + x); } );
耶,真方便!請(qǐng)注意我只是隨手創(chuàng)建了個(gè)函數(shù),甚至不用考慮怎么為它起名,只要拎著它的耳朵把它往一個(gè)函數(shù)里頭一丟就可以了。
當(dāng)你一想到作為參數(shù)的匿名函數(shù),你也許想到對(duì)那些對(duì)數(shù)組里的每個(gè)元素進(jìn)行相同操作的代碼。
var a = [1,2,3];
for (i=0; i<a.length; i++){
a[i] = a[i] * 2;
}
for (i=0; i<a.length; i++){
alert(a[i]);
}
常常要對(duì)數(shù)組里的所有元素做同一件事,因此你可以寫個(gè)這樣的函數(shù)來幫忙:
function map(fn, a){
for (i = 0; i < a.length; i++){
a[i] = fn(a[i]);
}
}
現(xiàn)在你可以把上面的東西改成:
map( function(x){return x*2;}, a );
map( alert, a );
另一個(gè)常見的任務(wù)是將數(shù)組內(nèi)的所有元素按照某總方式匯總起來:
function sum(a){
var s = 0;
for (i = 0; i < a.length; i++)
s += a[i];
return s;
}
function join(a){
var s = "";
for (i = 0; i < a.length; i++)
s += a[i];
return s;
}
alert(sum([1,2,3]));
alert(join(["a","b","c"]));
sum和join長得很像,你也許想把它們抽象為一個(gè)將數(shù)組內(nèi)的所有元素按某種算法匯總起來的泛型函數(shù):
function reduce(fn, a, init){
var s = init;
for (i = 0; i < a.length; i++)
s = fn( s, a[i] );
return s;
}
function sum(a){
return reduce( function(a, b){ return a + b; }, a, 0 );
}
function join(a){
return reduce( function(a, b){ return a + b; }, a, "" );
}
許多早期的編程語言沒法子做這種事。有些語言容許你做,卻又困難重重(例如C有函數(shù)指針,但你要在別處聲明和定義函數(shù))。面向?qū)ο笳Z言也不確保你用函數(shù)可以干些啥(把函數(shù)當(dāng)對(duì)象處理?)。
如果你想將函數(shù)視為一類對(duì)象,Java要求你建立一個(gè)有單方法的對(duì)象,稱為算子對(duì)象。許多面向?qū)ο笳Z言要你為每個(gè)類都建立一個(gè)完整文件,像這樣開發(fā)可真叫快。如果你的編程語言要你使用算子對(duì)象來包裝方法(而不是把方法本身當(dāng)成對(duì)象),你就不能徹底得到現(xiàn)代(動(dòng)態(tài))編程語言的好處。不妨試試看你可否退貨拿回些錢?
不用再寫那些除了經(jīng)過一個(gè)數(shù)組對(duì)每個(gè)元素做一些事情之外一無是處的函數(shù),有什么好處?
讓我們看回map函數(shù)。當(dāng)你要對(duì)數(shù)組內(nèi)的每個(gè)元素做一些事,你很可能不在乎哪個(gè)元素先做。無論由第一個(gè)元素開始執(zhí)行,還是是由最后一個(gè)元素執(zhí)行,你的結(jié)果都是一樣的,對(duì)不?如果你手頭上有2個(gè)CPU,你可以寫段代碼,使得它們各對(duì)一半的元素工作,于是乎map快了兩倍。
或者,發(fā)揮一下想像力,設(shè)想你在全球有千千萬萬臺(tái)服務(wù)器分布在全世界的若干個(gè)數(shù)據(jù)中心,你有一個(gè)真的很大很大的數(shù)組,嗯,再發(fā)揮一下想像力,設(shè)想這個(gè)數(shù)組記錄有整個(gè)互聯(lián)網(wǎng)的內(nèi)容。還了,現(xiàn)在你可以在幾千臺(tái)服務(wù)器上同時(shí)執(zhí)行map,讓每臺(tái)服務(wù)器都來解決同一個(gè)問題的一小部分。
那么在這個(gè)例子里面,編寫一段非常快的代碼來搜索整個(gè)互聯(lián)網(wǎng)這個(gè)問題,其實(shí)就和用一個(gè)簡單的字符串搜索器(算子)作為參數(shù)來調(diào)用map函數(shù)一樣簡單了。
希望你注意到一個(gè)真正有意思的要點(diǎn),如果你想要把map/reduce模式變成一個(gè)對(duì)所有人都有用,對(duì)所有人都能立刻派上用場的技術(shù),你只需要一個(gè)超級(jí)天才來寫最重要的一部分代碼,來讓map/reduce可以在一個(gè)巨大的并行計(jì)算機(jī)陣列上運(yùn)行,然后其他舊的但是一向在單一個(gè)循環(huán)中運(yùn)行良好的代碼,仍可以保持正確的運(yùn)行,惟一的差別只是比原來單機(jī)運(yùn)行快了n倍。這意味著它們都一不留神突然變成可以被用來解決一個(gè)巨大的問題的代碼。
讓我再啰嗦一下,通過把“循環(huán)”這個(gè)概念加以抽象,你可以把用任何你喜歡的方式來實(shí)現(xiàn)“循環(huán)”過程,包括可以實(shí)現(xiàn)讓循環(huán)迭代速度隨著硬件計(jì)算能力保持令人滿意的同步增長。
你現(xiàn)在應(yīng)該可以明白不久為何對(duì)那些對(duì)除了Java之外什么都沒被學(xué)過的計(jì)算機(jī)系學(xué)生表示不滿了:( http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html) :
Without understanding functional programming, you can't invent MapReduce, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their 6.001-equivalent programming class that purely functional programs have no side effects and are thus trivially parallelizable. The very fact that Google invented MapReduce, and Microsoft didn't, says something about why Microsoft is still playing catch up trying to get basic search features to work, while Google has moved on to the next problem: building Skynet^H^H^H^H^H^H the world's largest massively parallel supercomputer. I don't think Microsoft completely understands just how far behind they are on that wave.
不理解函數(shù)式編程,你就發(fā)明不了MapReduce這個(gè)讓Google的計(jì)算能力如此具有可擴(kuò)展性的算法。Map和Reduce這兩個(gè)術(shù)語源自Lisp語言和函數(shù)式編程……(這是另一篇文章的內(nèi)容,emu也不是很理解其中的各種說法的來龍去脈,就不翻譯了)
我希望你現(xiàn)在明白,把函數(shù)當(dāng)成基本類型的(動(dòng)態(tài))編程語言能讓你在編程過程中更好的進(jìn)行抽象化,也就是使代碼精悍、功能更內(nèi)聚、更具可重用性及更具有擴(kuò)展性。很多的Google應(yīng)用使用Map/Reduce模式,因此一有人對(duì)其優(yōu)化或修正缺陷,它們就都可以從中得益。
我準(zhǔn)備要再羅嗦一下,我認(rèn)為最有生產(chǎn)力的編程語言莫過于能讓你在不同層次上都可以進(jìn)行抽象化的。老掉牙的FORTRAN 語言以前是不讓你寫函數(shù)的注。C 有函數(shù)指針,可是它們都非常丑丑丑丑丑丑丑丑陋,不允許匿名聲明,又不能在用它們時(shí)實(shí)現(xiàn)它們而偏偏要放在別處去實(shí)現(xiàn)。Java讓你使用算子對(duì)象,一種更丑陋的東西。正如Steve Yegge所述,Java是個(gè)名詞王國 (http://steveyegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)。
作者注:這里提起了FORTRAN,不過我上次使用FORTRAN是27年前的事了。FORTRAN是有函數(shù)的,我碼字那會(huì)兒腦子里面想的大概是GW-BASIC語言。(emu注,basic確實(shí)只有所謂的子程序和go-sub語句,作用只是重新組織代碼結(jié)構(gòu)而已,沒有參數(shù)和調(diào)用堆棧,因此沒有真正的函數(shù)調(diào)用)
譯者注:原作者起了《你的編程語言可以這樣做嗎》這個(gè)標(biāo)題其實(shí)并不是這篇文章的真正價(jià)值所在,我轉(zhuǎn)這篇文章也不是因?yàn)樵髡呖梢园颜Z言的初級(jí)技巧玩得轉(zhuǎn),而是因?yàn)檫@是一篇map/reduce模型的示范。
相關(guān)文章
JS表單驗(yàn)證方法實(shí)例小結(jié)【電話、身份證號(hào)、Email、中文、特殊字符、身份證號(hào)等】
這篇文章主要介紹了JS表單驗(yàn)證方法,結(jié)合實(shí)例形式總結(jié)分析了常用的表單驗(yàn)證技巧,包括電話、身份證號(hào)、Email、中文、特殊字符、身份證號(hào)等驗(yàn)證方法,需要的朋友可以參考下2017-02-02JavaScript(JS) 壓縮 / 混淆 / 格式化 批處理工具
本工具所有的功能實(shí)現(xiàn)都是由 ttp://jscompress.sinaapp.com/api 處理.(包括現(xiàn)在可以使用的這個(gè)在線壓縮)2010-12-12javascript中setTimeout和setInterval的unref()和ref()用法示例
本文通過一個(gè)小例子想大家講解了setTimeout和setInterval的unref()和ref()用法和使用環(huán)境,代碼很簡潔,有需要的小伙伴自己參考下吧。2014-11-11js onmousewheel事件多次觸發(fā)問題解決方法
做一個(gè)首屏和第二屏之間滾動(dòng)鼠標(biāo)滾輪就可以整平切換的效果,遇到了很多問題,下面是問題解決方法2014-10-10LBS blog sql注射漏洞[All version]-官方已有補(bǔ)丁
LBS blog sql注射漏洞[All version]-官方已有補(bǔ)丁...2007-08-08