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

for 循環為何可恨?

開發 后端
Java的閉包(Closure)特征最近成為了一個熱門話題。一些精英正在起草一份議案,要在Java將來的版本中加入閉包特征。然而,提議中的閉包語法以及語言上的這種擴充受到了眾多Java程序員的猛烈抨擊。

Java的閉包(Closure)特征最近成為了一個熱門話題。一些精英正在起草一份議案,要在Java將來的版本中加入閉包特征。然而,提議中的閉包語法以及語言上的這種擴充受到了眾多Java程序員的猛烈抨擊。

不久前,出版過數十本編程書籍的大作家Elliotte Rusty Harold發表了對Java中閉包的價值的質疑。尤其是他問道“ for 循環為何可恨?”:

我不知道,有些人這么著急的要把 for 循環消滅掉,他們反對的究竟是什么?這已經不是第一次或第二次計算機學界的理論家們起來反對 for 循環(或類似的東西)了。
 

如果只說Elliotte質疑不起眼的閉包的價值,這是不公平的。他主要抱怨是,在讀了另一位著名人物、獲得過Jolt 大獎并創造過最高銷售記錄的《Better, Faster, Lighter Java》的作者Bruce Tate的最近的關于此主題的專題后,他看不出閉包在Java中有什么價值。(Bruce用Ruby做的例證):

表 1. 最簡單的閉包

  1. 3.times {puts "Inside the times method."}  
  2.  
  3. 結果:  
  4. Inside the times method.  
  5. Inside the times method.  
  6. Inside the times method. 

times是3這個對象上的一個方法。它把閉包中的代碼執行了3次。{puts "Inside the times method."}是閉包。它是一個匿名函數,把它傳入times方法,打印出靜態句子。相比起傳統的for循環語句,這樣的代碼顯得更緊湊,更簡單,如表2中所示:

表 2: 非閉包的循環

  1. for i in 1..3 
  2. puts "Inside the times method." 
  3. end 

由于這種毫無生氣的對閉包的介紹,我也很難看出它的真正價值。這首個比較,充其量也就能體現出一種微妙的差別。Bruce在developerWorks上的文章里的其它的例子也大多是價值不大的,要么含糊不清,要么缺乏啟發意義。

對于這種Ruby風格的閉包給Elliotte帶來的困惑,我不打算進一步評論;對這種問題過于挑剔毫無意義。我也不想討論目前的關于Java中的閉包的語法的提議的爭論,包括Java中是否應該有閉包這樣的大問題。在這樣的爭論中我沒有立場,說實話,我是不在乎這些問題如何或何時被解決。

雖然如此,Elliotte卻提出了一個重要的問題:for 循環為什么可恨?

下面是一個常見的例子:

  1. double sum = 0;  
  2. for (int i = 0; i < array.length; i++) {  
  3. sum += array[i];  

這有什么問題?我編了很多年的程序,我對這種語法一眼掃過去很舒服;很顯然,它是把一個數組里的值加到一起。但當去真正的閱讀這段代碼時,這四行代碼里大概散布著30多個標記符號需要我去分析處理。不錯,有些字符可以通過語法簡寫方式來縮減。但為了這樣一個簡單的加法,你需要寫出一堆東西,還要保證寫的正確。

憑什么這樣說?下面是Elliotte的文章里另外一個例子,原文拷貝:

  1. String s = "";  
  2. for (int i = 0; i < args.length; i++) {  
  3. s += array[i];  

看見了里面的錯誤嗎?如果這代碼編譯通過,并通過的代碼審查,你可能需要數周才會發現這樣的bug,再數周才能制作出補丁。這些只是簡單的for循環。想象一下,當for循環體變得越來越大,甚至有嵌套時,事情會變得多么的復雜。(如果你仍舊不擔心這樣的bug,認為這只是拼寫錯誤,那么你就想想有多少次在for循環里你是這樣的。)

如果你能夠把一個簡單的for循環寫成一行,帶有更少的重復和更少的字符,這樣不僅更容易閱讀,也更容易書寫。因為這樣更簡潔,引入bug的機會就更少,當bug出現時,也更容易被發現。

那閉包對此有何幫助?下面是第一個例子,用Haskell語言寫成的:

  1. total = sum array 

哈哈,我是在說謊。sum函數并沒有使用閉包。它是按照fold的方式定義的,而fold是接受閉包的:

  1. total = foldl (+) 0 array 

下面是第二個例子,很常見,而且使用了閉包:

  1. s = concat array  
  2. s = foldr (++) [] array 

我承認,使用這些叫做foldl 和 foldr 樣子古怪的函數來解釋閉包的作用,這對那些更熟悉for循環的程序員來說沒有多大意義。但是,這幾個函數卻能突出for循環的關鍵弊端:它把三種獨立不同的操作合并到一起了——過濾,歸納和轉換。

上面的這兩種for循環,它們的目標是接收一個數值列表,把它們歸納成一個值。函數式編程的程序員稱這些操作為“folds(合并)”。一個fold運算的過程是,首先要有一個操作(一個閉包)和一個種子值,還有使用list里的第一個元素。這個操作被施加到種子值和list里的第一個元素上,產生出一個新的種子值。fold運算然后把這個操作運用到新種子值和list里的下一個元素上,一直這樣,直到最后一個值,最后一次操作的結果成為fold運算的結果。

下面是一個演示:

  1. s = foldl (+) 0 [123]  
  2. = foldl (+) (0 + 1) [23]  
  3. = foldl (+) 1 [23]  
  4. = foldl (+) (1 + 2) [3]  
  5. = foldl (+) 3 [3]  
  6. = foldl (+) (3 + 3) []  
  7. = foldl (+) 6 []  
  8. 6 

Haskell語言里提供了很多fold函數;foldl函數從list的第一位開始運算,依次反復到最后一個,而foldr函數,它從list的最后一個函數開始運算,從后往前。還有很多其它相似的函數,但這兩個是最基本的。

當然,folds是一些非常基本的運算,如果拋棄for循環而以各種形式的foldl 和 foldr 咒符來替換,你會很困惑。事實上,更高級的操作,例如sum, prod 和 concat都是以各種folds定義的。當你的代碼以這種高級的歸納操作運算來編寫時,代碼會變得更簡潔,更易讀,更易寫,更易懂。

當然,并不是所有的for循環都是歸納操作。看看下面這個:

  1. for (int i = 0; i < array.length; i++) {  
  2. array[i] *= 2;  

這是一個轉換操作,函數式編程的程序員稱之為map操作:

  1. new_array = map (*2) array 

map函數的工作方式是,它會檢查list里的每個元素,將一個函數應用到每個元素上,形成一個新的list,里面是新的元素。(有些語言里的這種操作是原位替換)。這是一個很容易理解的操作。sort函數的功能相似,它接受一個list,返回(或修改)一個list。

第三種類型的for循環是過濾。下面是個例子。

  1. int tmp[] = new int[nums.length];  
  2. int j = 0;  
  3. for (int i = 0; i < nums.length; i++) {  
  4. if ((nums[i] % 2) == 1) {  
  5. tmp[j] = nums[i];  
  6. j++;  
  7. }  

這是一個非常簡單的操作,但使用了for循環和兩個獨立的計數器后,毫無必要的復雜表現把事實真相完全掩蓋了。如果過濾是一種基本的操作,它應該像一個fold或一個map那樣,而事實上,它是的:

  1. odds = filter (\i => (i `mod` 2) == 1) nums  
  2. odds = filter isOdd nums -- 更常用的形式 

從核心上講,這就是為什么for循環有問題:它把(至少)三種獨立的操作合并到了一起,但重點卻關注了一個次要細節問題:遍歷一系列的值。而事實上,fold,map 和 filter是處理一個數據list的三種不同的操作,它們應該被分別處理。采用把閉包傳入循環內的方式,我們能更容易的把what 從 how 中分離出來。每次遍歷一個list時我都會使用一個匿名函數,或復用通用的函數(例如 isOdd, (+) 或 sqrt)。

雖然閉包并不是一個很深奧的概念,但當它深深的烙進了一種語言和它的標準庫中時,我們不需要使用這些低級的操作搞的代碼混亂不堪。相反,我們可以創建更高級的運算,做我們想要的事,比如sum and prod。

更重要的,以這些概念思考問題會使我們更容易思考更復雜的操作,比如變換一個tree,過濾一個vector,或把一個list合并成一個hash。

在最后,Elliotte還提到了一些關于在多核處理器上并行執行的問題,說像3.times {...}這樣的代碼會比 for 循環效率“差”。不幸的是,我想他沒說到點上。不錯,有一些運算需要序列化,有一些可以并行。但是如果你只基于一個for循環,很難判斷出哪些歸為哪類,這是一個復雜的編譯器優化問題。如果你把一個可能進行并行運算的操作(例如map 和 filter)分解成連續的運算(例如foldl 和 foldr),編譯器更容易從中做出判斷。不僅如此,如果你比編譯器更了解你的數據,你可以顯式的要求一個map操作被順序執行或并行執行。

英文原文鏈接:

本文轉自:http://www.aqee.net/whats-wrong-with-the-for-loop/

【編輯推薦】

  1. 畫虎畫皮難畫骨,編程編碼難編譯
  2. 詳解Java類的生命周期
  3. Java代碼編寫的30條建議
  4. Java Excel API及詳細教程
  5. Java集合框架和數組的排序
責任編輯:林師授 來源: 外刊IT評論
相關推薦

2016-11-16 13:51:46

數據庫NoSQL大數據

2024-12-02 10:47:45

Go語言Java

2019-07-16 09:33:06

Google軟件硬件

2009-12-10 16:26:49

GNULinux

2022-06-19 14:10:48

開源軟件企業Linux 基金會

2022-03-01 18:03:06

Spring緩存循環依賴

2022-12-02 12:01:30

Spring緩存生命周期

2013-09-13 17:17:29

騰訊云

2009-01-07 16:14:09

瀏覽器IEFirefox

2024-03-26 11:45:29

2010-11-11 11:21:17

2012-11-21 10:10:06

OpenStack網絡虛擬化

2013-03-15 13:45:58

2022-05-10 16:37:25

零信任網絡安全

2023-02-15 08:00:00

2013-01-31 18:52:58

CiscoACEF5

2011-12-27 10:12:59

SafariChrome

2013-07-29 11:04:29

App升級

2015-11-25 09:29:41

2009-05-25 13:50:28

Linux桌面走俏
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日日夜夜精品视频 | 久久国产精品无码网站 | 久久99精品久久久久蜜桃tv | 黑人精品 | 日韩乱码在线 | 成人精品视频在线观看 | 国产精品一区二区三 | 亚洲精品视频一区 | 范冰冰一级做a爰片久久毛片 | 日本又色又爽又黄的大片 | 欧美国产日韩在线观看 | 欧美日韩国产精品一区 | 国产成人精品免高潮在线观看 | 国产资源视频 | 中文字幕av网 | 天天干干 | 久久久久国产一区二区三区 | 波多野结衣在线观看一区二区三区 | 亚洲欧美一区二区三区在线 | 国产精品欧美一区二区三区不卡 | 99亚洲精品 | 久久九九网站 | 欧美a在线看 | av网站在线免费观看 | 久久久久久免费精品一区二区三区 | 久久精品亚洲成在人线av网址 | 久久久久国产精品 | 三级视频国产 | 日韩中文不卡 | 国产福利在线播放麻豆 | 日韩精品中文字幕在线 | 欧美一级做性受免费大片免费 | 成人国产a| 最新日韩精品 | www.日韩| 国产精品久久久久一区二区三区 | av av在线 | 日韩精品在线视频免费观看 | 日韩高清一区 | 国产精品爱久久久久久久 | 色婷婷亚洲 |