當(dāng)你一想到作為參數(shù)的匿名函數(shù),你也許想到對那些對數(shù)組里的每個元素進行相同操作的代碼。
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]); }
常常要對數(shù)組里的所有元素做同一件事,因此你可以寫個這樣的函數(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 );
另一個常見的任務(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長得很像,你也許想把它們抽象為一個將數(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)對象處理?)。
如果你想將函數(shù)視為一類對象,Java要求你建立一個有單方法的對象,稱為算子對象。許多面向?qū)ο笳Z言要你為每個類都建立一個完整文件,像這樣開發(fā)可真叫快。如果你的編程語言要你使用算子對象來包裝方法(而不是把方法本身當(dāng)成對象),你就不能徹底得到現(xiàn)代(動態(tài))編程語言的好處。不妨試試看你可否退貨拿回些錢?
不用再寫那些除了經(jīng)過一個數(shù)組對每個元素做一些事情之外一無是處的函數(shù),有什么好處?
讓我們看回map函數(shù)。當(dāng)你要對數(shù)組內(nèi)的每個元素做一些事,你很可能不在乎哪個元素先做。無論由第一個元素開始執(zhí)行,還是是由最后一個元素執(zhí)行,你的結(jié)果都是一樣的,對不?如果你手頭上有2個CPU,你可以寫段代碼,使得它們各對一半的元素工作,于是乎map快了兩倍。
或者,發(fā)揮一下想像力,設(shè)想你在全球有千千萬萬臺服務(wù)器分布在全世界的若干個數(shù)據(jù)中心,你有一個真的很大很大的數(shù)組,嗯,再發(fā)揮一下想像力,設(shè)想這個數(shù)組記錄有整個互聯(lián)網(wǎng)的內(nèi)容。還了,現(xiàn)在你可以在幾千臺服務(wù)器上同時執(zhí)行map,讓每臺服務(wù)器都來解決同一個問題的一小部分。
那么在這個例子里面,編寫一段非?斓拇a來搜索整個互聯(lián)網(wǎng)這個問題,其實就和用一個簡單的字符串搜索器(算子)作為參數(shù)來調(diào)用map函數(shù)一樣簡單了。
希望你注意到一個真正有意思的要點,如果你想要把map/reduce模式變成一個對所有人都有用,對所有人都能立刻派上用場的技術(shù),你只需要一個超級天才來寫最重要的一部分代碼,來讓map/reduce可以在一個巨大的并行計算機陣列上運行,然后其他舊的但是一向在單一個循環(huán)中運行良好的代碼,仍可以保持正確的運行,惟一的差別只是比原來單機運行快了n倍。這意味著它們都一不留神突然變成可以被用來解決一個巨大的問題的代碼。
讓我再啰嗦一下,通過把“循環(huán)”這個概念加以抽象,你可以把用任何你喜歡的方式來實現(xiàn)“循環(huán)”過程,包括可以實現(xiàn)讓循環(huán)迭代速度隨著硬件計算能力保持令人滿意的同步增長。
你現(xiàn)在應(yīng)該可以明白不久為何對那些對除了Java之外什么都沒被學(xué)過的計算機系學(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這個讓Google的計算能力如此具有可擴展性的算法。Map和Reduce這兩個術(shù)語源自Lisp語言和函數(shù)式編程……(這是另一篇文章的內(nèi)容,emu也不是很理解其中的各種說法的來龍去脈,就不翻譯了)
我希望你現(xiàn)在明白,把函數(shù)當(dāng)成基本類型的(動態(tài))編程語言能讓你在編程過程中更好的進行抽象化,也就是使代碼精悍、功能更內(nèi)聚、更具可重用性及更具有擴展性。很多的Google應(yīng)用使用Map/Reduce模式,因此一有人對其優(yōu)化或修正缺陷,它們就都可以從中得益。
我準(zhǔn)備要再羅嗦一下,我認(rèn)為最有生產(chǎn)力的編程語言莫過于能讓你在不同層次上都可以進行抽象化的。老掉牙的FORTRAN 語言以前是不讓你寫函數(shù)的注。C 有函數(shù)指針,可是它們都非常丑丑丑丑丑丑丑丑陋,不允許匿名聲明,又不能在用它們時實現(xiàn)它們而偏偏要放在別處去實現(xiàn)。Java讓你使用算子對象,一種更丑陋的東西。正如Steve Yegge所述,Java是個名詞王國 (http://steveyegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)。
作者注:這里提起了FORTRAN,不過我上次使用FORTRAN是27年前的事了。FORTRAN是有函數(shù)的,我碼字那會兒腦子里面想的大概是GW-BASIC語言。(emu注,basic確實只有所謂的子程序和go-sub語句,作用只是重新組織代碼結(jié)構(gòu)而已,沒有參數(shù)和調(diào)用堆棧,因此沒有真正的函數(shù)調(diào)用)
譯者注:原作者起了《你的編程語言可以這樣做嗎》這個標(biāo)題其實并不是這篇文章的真正價值所在,我轉(zhuǎn)這篇文章也不是因為原作者可以把語言的初級技巧玩得轉(zhuǎn),而是因為這是一篇map/reduce模型的示范。
出處:joelonsoftware.com
責(zé)任編輯:moby
上一頁 你的編程語言可以這樣做嗎? [1] 下一頁
◎進入論壇網(wǎng)絡(luò)編程版塊參加討論
|