Scala中何時應當使用Var變數?

題主最近在Coursera上學習Scala的課程,在一次課程的作業中,有一個函數的實現在Instruction中特別指出使用while循環實現,然而之前的作業中是從未使用while循環的,同樣的功能通常使用遞歸形式或者foreach、map等函數來實現,避開使用變數。

這時突然提出要用while循環來做,那就避免不了使用var變數了,然而這完全與函數式編程的理念相悖。於是有小夥伴提出了問題,最後的幾張圖是教員的回復,大概意思是說Scala中for「循環」會被展開成foreach的調用,中間會有成噸的性能損耗,而且foreach底層實現也是用while循環,乾脆直接用while循環算了。。。

另外也有小夥伴表示,Scala對與簡單for循環沒有優化性能極差,是一個眾所周知的問題,並甩出了一個ISSUE的鏈接。。。https://issues.scala-lang.org/browse/SI-1338

然後題主在完成最近的作業中也出現了相似的情形,於是想起了這個問題。

我認為使用遞歸等方法來繞過對變數的使用,目的是為了維護純函數式風格,此時不在意性能上的損耗。然而當考慮到性能的時候,是否應當使用變數是一個很值得推敲的問題,我的想法是在實現底層的函數的時候,如果使用變數可以簡化代碼並且不會影響函數的並行的話,是可以使用的,而在高層的實現的時候盡量不去使用變數。請問各位老司機如何看待這個問題?

/* 以下是教員對於使用while循環的回答

*/


謝邀。

個人認為,既然Scala提供了var與val兩種不同的變數定義方式,也就說明Scala中可以靈活根據場景選擇變數和不可變數。

對於循環的場景,函數式語法的常規做法是遞歸,依賴編譯器/解釋器的尾遞歸等優化技術來解決性能問題。命令式語言的做法是中間變數,開發者自行優化。

Scala的for語句,就像題主所說,對於簡單的循環場景,並不是一個很好的解決方案。所以你可以在Scala的基礎庫中看到很多使用var的命令式循環。

對於上層,請原諒我原先是一個Java開發,如果是常見的遍歷,轉換,用for,用Scala集合提供的方法都可以。但是碰到一些比較細節的問題,不管是底層庫還是實際開發都建議使用更容易控制,偏命令式的方式來做。畢竟Scala不是純函數式語言。


我想我提幾個關鍵字出來給題主應該就足夠了, 網上相關材料足夠多, 寫的也都很好, 我就不傳二手翻譯了.

referential transparency, substitution model. 這兩個概念在課程中應該都有提及.

函數式編程推崇purity的理由之一就是, 消除了變數後, 程序更加容易推理.

在函數內部使用var 無可厚非, 但盡量做到不產生對函數外部可見的副作用, 這樣既能起到優化的目的, 又可以不破壞purity.


所有情況都不需要while和局部變數var,可以用foldLeft和尾遞歸代替。

foldLeft性能差一點,適合性能不敏感的場合。尾遞歸性能和while差不多。

可讀性上來講,foldLeft和尾遞歸不比while差,但可能初學者需要一點時間適應一下。

從保證程序的正確性上講,無論尾遞歸還是while都有可能寫錯,寫成死循環,foldLeft卻能保證循環的次數,不可能寫成死循環。再者,var還有額外的風險,如果寫錯了的話,可能會不小心改錯了值。

所以我推薦所有場合都不用while和局部變數var,一律用foldLeft和尾遞歸即可。

------------------------------------------

我只強調「局部變數var」一律用foldLeft和尾遞歸代替,是因為類裡面的var和可變數據結構更棘手些,不可一概而論。

棘手的原因有三點:

  1. 類里的var造成的潛在危害很大,可能在並發或者重入的情形導致很難解決的bug。

  2. 不容易完全消除類里的var,如果一定要消除,會導致代碼變長、性能變差。

  3. 如果你本來就是為可變模型建模,用var最順手。

所以,類里的var是架構設計問題,而不只是編碼問題。

舉個例子,我在Binding.scala中做了一個艱難的設計決策。我讓Binding.scala的內部數據在初始構建數據綁定表達式時不可變,但在運行時可變。

我在數據綁定表達式的生命周期管理一節寫到:

簡而言之,Binding.scala 將函數分為兩類:

  • 用戶定義的 @dom 方法,用於產生不帶副作用的純函數式的數據綁定表達式。
  • 調用 dom.render 以及 Binding.watch 的函數,自動地管理所有副作用。

可能某些人會詬病Binding.scala的數據模型不夠「purity」,因為運行起來會變。

但實際上用起來,這種混合做法卻很順暢。因為用戶定義的數據綁定表達式只描述數據之間的關係,而不修改任何變數,所有的變數修改都由Binding.scala框架負責。那麼,只要我寫的框架沒有bug,用戶就很難自己寫出bug。


在我看來,這是個審美問題,在任何場景都應該儘可能避免外部負作用,函數的可組合型要求在儘可能多的層面上提供優化推理的空間,負作用壓縮推理優化的空間。


推薦閱讀:

scala中web開發框架,哪一個能最後一統scala天下?lift or play

TAG:Scala | 函數式編程 |