Scala 是一門怎樣的語言,具有哪些優缺點?


由於我沒有博客,暫時就把這裡當做博客,寫一點我自己對Scala的心得體會。

以前在這個版塊也答過關於Scala的問題,但那更多的是知識普及,而沒有談Scala是什麼,做什麼,以及有怎樣的特點。

Scala可怕的地方在於人人都能對它說上一二,但是不一定每個人都能明白。查看這個版塊的帖子,有人把它當做Java的延伸版(一個UPenn賓大的學生Justin Kim ——此人目前在沃頓混得風生水起,當著我的面說Scala是在JVM上的腳本語言),有人把它當做JVM上的C++,有人覺得這是面對對象語言和函數語言的簡單混合,有人覺得這就是Haskell,而且也還不如Haskell強。對Scala的偏見(或者是錯誤的見地)達到了很高的地步,Martin Odersky馬丁·奧德斯基(Scala的發明者,EPFL教授)在今年夏天的Scala Day舊金山大會上發出了這張著名的玩笑照片:

這個圖片上的翻譯是:「Scala唯一的作用是將人引向Haskell」(原諒我沒法完全直譯)。馬丁·奧德斯基以此作為一個笑話,說他該把Scala改一下名字,叫做Hascalator,還請人設計了一個Logo。視頻連接:https://www.parleys.com/tutorial/scala-where-came-from-where-its-going,需要快進到11分鐘到馬丁·奧德斯基出場。

不同的語言有不同的特點,同時也帶來不同的優勢。如果不能理解Scala的特點,就不可能知道如何運用Scala,以及發揮其最大的優勢。一些語言有很顯而易見的優勢,也很容易理解,比如Python,Python的哲學(Zen of Python PEP 20 -- The Zen of Python),我很早的時候曾經覺得有道理,尤其是One way to do it(一種方法做一件事情),理由是對任何任務,雖然可以採用很多方法,但總有最好的一種方法,通過在語言或者哲學層面這樣定義後,能簡化程序員的任務,從而達到提高效率的方法。但經過一段時間的思考後,我突然發現Python其實並不是「一種方法做一件事」的哲學,而是「一種方法做一百萬件事情」的哲學:極其有限的數據結構(只有四個: List, Tuple, Dictionary, Sets),以及不能查看時間複雜度的訪問方法,比如鼓勵人們使用for x in list。

這種處理方式能達到Python最初的打算:發明一種每個人都能使用的簡易語言,但是對於追求速度和效率的程序員而言,這幾乎是略帶噩夢性質的。當然,這不是說Python很慢,通過各種優化(比如NumPy/SciPy中的),以及Cython這樣的將Python直接翻譯為C/C++語言又重新通過C_Module方式讀回Python環境的編譯器,性能可以得到不少提升,但是仍舊,Python並不追求快。

再舉一個語言的例子:Java。Java的特性或者優勢何在?Java的第一個優勢在於它是第一個系統提供模塊化(module)設計的語言(在此之前有Smalltalk存在,該貨是OOP的鼻祖)。在Java之前,炒程序員魷魚是很困難的事情,那些C/C++程序員,以及而且尤其是那些Lisp程序員,一旦炒掉他們,新來的人沒有十天半個月,甚至半年,是不可能搞懂前任人士的代碼的。每個人的代碼有自己的邏輯,自己的思路,寫上個數萬行任誰來看都頭疼。這也是為什麼Paul Graham保羅·格雷厄姆(寫了《黑客與畫家》)講他給雅虎做了一個用Lisp寫成的在線商店的案例,在他離開後,雅虎根本沒法維護他寫的代碼,因為數萬行Lisp沒人能弄得很清楚。

Java的模塊化,給企業、大公司帶來了第一道曙光,模塊化之後,這些公司不再給程序員一整個任務,而是一大塊任務的一小塊。介面一定義,虛擬類一定義,換誰上都可以,管你是保羅·格雷厄姆這樣的明星程序員,還是一個新來的大學生,程序員不聽話就直接開除,反正模塊化之後,開除程序員的成本大大降低,這也是為什麼谷歌、甲骨文(這貨最後收購了Java)一類的公司大規模的推崇Java,還一度提出了模塊化人事管理的理念(把人當模塊化的積木一樣隨時移進移出)。

過度企業化後,這延展出了Java的第二個特性,束縛手腳。保羅·格雷厄姆在《黑客與畫家》中寫道,Java屬於BD(捆綁與束縛)類型的語言。為何束縛手腳?因為要讓新手和明星程序員寫出類似質量的代碼,儘可能的抹消人的才華對程序的影響。不同於C/C++,老手和新手寫出的Java代碼不會有上百倍的耗時差距。但同樣也導致了Java的一個弱點——不容易優化。很多優化Java代碼的程序員必須要對JVM(虛擬機)進行優化,實際上增大了很多任務難度。

通過Python和Java這兩個語言的優缺點,返回來看Scala,就能瞬間明白Scala的定位了。

首先,Scala不把程序員當傻子。當馬丁·奧德斯基宣布Scala 2.12(http://www.scala-lang.org/news/roadmap-next)將要簡化語法,推出Scala "Don Giovanni"項目的時候,在視頻中說的很清楚:「Scala現在是為聰明人創造的,以後也是為聰明人服務的。」所以不同於Python讓程序員用一種方法做所有事情,Scala提供一整套工具,讓程序員自由選擇,無論是mutable數據結構,immutable數據結構,並行(parallel)數據結構。然後在這些選擇中,Scala再針對他們進行演算法層面的特殊優化。Scala相信程序員的聰明才智,讓程序員自行選擇合適的結構,以針對變化萬千的任務需求,這點是Scala做得極好的地方。

再者,有人會說immutable數據結構佔用內存,或者速度很慢。這是真的,但這不是Scala的錯,而是這些結構就是這樣定義的。可以看看這個視頻:https://www.parleys.com/tutorial/scala-collections-performance 這裡講的是Scala集合的運行速度,是一個來自Goldman Sachs的程序員講他們為Java寫的集合庫(GSCollection)速度和內存消耗,但同時比較了gs-collection(goldmansachs/gs-collections · GitHub),Java,和Scala庫的速度。最後Scala的可變集合mutable原生庫完爆Java,和gs-collection基本持平。

Scala的第二個優勢,相較於Java而言,則是相信程序員的優化能力。在Scala with Style講話中(https://www.youtube.com/watch?v=kkTFx3-duc8),馬丁·奧德斯基說:「很多程序員會告訴我,他們一般會重構他們的Scala代碼兩三次,甚至三四次。」這聽起來似乎非常的沒有效率,但Scala就是這樣的語言,每一次重構,代碼的性能或者是可讀性都會有極高的提升。

之前就有人提到過,Scala新手和老手寫出來的代碼完全會呈現兩種不同的風格,甚至新人根本不能讀懂有經驗的Scala程序員所寫的代碼,有人於是戲稱:「太好了,這樣的話我們部門的實習生就不能亂碰我寫的代碼啦!」但其實不僅風格不同,執行效率差距也一定是巨大的。Scala提供一整套工具,但是要明白什麼時候用拿一種工具,哪些演算法能夠隨意調用,哪些演算法不能,這一定要依靠經驗、研究和學習以及對源代碼的理解才能得知。最簡單的例子,Scala的foreach()方法是高度優化過了的(尤其針對Range結構和Vector結構),但是fold()就不一定了。或者當受到誘惑想用zipWithIndex()的時候,一定要明白這是兩次循環,最好改用Vector(...).indices.foreach()的方法,或者用.view來推遲執行。

像這樣的地方還有很多。所以在這個層面上來講,簡直和C++非常的相似。從另外一個層面來講,不僅僅是要理解語言層面的優化,Scala作為一個社區而言,是非常追求運行速度的。Ruby社區就完全不同了,Ruby曾經是推特的主要語言。推特的團隊找到了Ruby團隊,說,你們能不能讓Ruby運行的快一點,我們有這個這個和這個建議。Ruby直接把這些建議拒絕了,因為它們會增加語言複雜度,讓Ruby不能繼續做一個「fun」(好玩)的語言。而Python直接就立志做一個「Simple」(簡單)的語言了。於是推特只好將後台換做Scala和Java的結合。有一位在推特工作的知乎友人在我的一個回答下留言說推特換用Scala後,TypeSafe(Scala的母公司)還送去了一個蛋糕。

為了追求速度,Scala社區是絕對不會管所謂的「簡單」或者是「好玩」,怎樣有效率就怎樣弄。與其專註於JVM的改進,Scala社區大部分在編譯器上下功夫,比如很著名的Miniboxing(Miniboxing),這是一個編譯器增進器。Miniboxing做的是什麼呢?只做一件事:防止auto-boxing和auto-unboxing。所有的泛型,尤其是原生類泛型(Primitive Types),諸如Int、Double等等,在進行各種操作的時候會自動取出和裝回它們所屬的類中去——這個我解釋的不太好,但是可以看這裡(Java 自動裝箱與拆箱(Autoboxing and unboxing))。

Miniboxing這樣的插件可以讓所有的原生類泛型再也不用自動裝拆箱,從而將Scala的運行速度提升1.5倍到22倍(http://scala-miniboxing.org/benchmarks.html)。當然這樣的東西可不是白來的,這是馬丁·奧德斯基的PhD博士學生做的一個研究項目,然後為OOPSLA寫了一篇論文(http://dl.acm.org/citation.cfm?id=2509537),所以怪不得這玩意Scala可以有,但其他語言想要有都沒有。

另一個Scala的很大優勢就是所謂的Macro——宏。宏本身作為元編程而言,其實和運行速度是沒有什麼太大關係的,反而,因為對反射(Reflect)的利用,可能會影響到速度。但Scala社區對宏的理解顯然和最初的設計理念有偏差。因為Scala本身是沒有傳統意義的循環的(for-loop),所以很多時候循環必須利用while或者foreach。但是部分追求效率的Scala程序員們利用宏為Scala寫了一個傳統循環,叫做cfor,被收錄在Spire(non/spire · GitHub)數學計算庫中。cfor的寫法如下:

import spire.syntax.cfor._

// print numbers 1 through 10
cfor(0)(_ &< 10, _ + 1) { i =&>
println(i)
}

而這玩意運行效率如何呢?https://www.chrisstucchio.com/blog/2014/learning_spire_cfor.html 文章中做了一次測評,將cfor和zip寫的一個演算法作比較——在公布結果之前,我想說的是,zip並不是一個高度優化的方法,所以本身就慢很多,cfor用了26.1毫秒運行,zip方法用了7.4 秒運行,這幾乎是284倍的速度差距。

通過這兩點,Scala的一個優勢就很明顯了——多樣化。當需要寫簡單的代碼,像Python一樣當腳本語言使用時,Scala提供大量的原生方法和數據結構,可以很輕鬆的寫出比較複雜的操作。但當需要速度的時候,又可以通過重構來獲取數十倍或者上百倍的速度提升。通過Miniboxing一類的編譯器增強器,Scala在某些操作的速度是必定超過Java的。

Scala的第二個優勢就是——一幫勤勞勇敢的PhD博士生。二十一世紀的程序語言和二十世紀的程序語言已經不能比擬了。那個年代的普通人(甚至是學生)還能任意發明一下語言,稍微把編譯器優化幾次就能上得了廳堂(比如那一大堆Lisp方言),到了這個年代,編譯技術已經達到了很複雜的程度(虛擬機技術也是如此),優化和語義理解,程序語言的定義與延展,再也不是隨便任何人都能搞定的工作了。作為編程語言方面的教授,馬丁·奧德斯基不斷的將最前沿的學術界成果轉移到Scala這個語言中,還讓他的博士學生髮展出新的,讓語言運行得更快的方法,這些都是其他語言,尤其是Python、Ruby、甚至是Go都沒有的優勢。

當然,說了這麼多,總會有人說了,Scala如果像C++一樣難,又追求速度的話,為什麼不直接去學C++,原因很簡單——現在有很多在JVM上面寫成的軟體啊!大家又不是Haskell程序員,壓根不打算一切自己寫吶。


說句理智一點的話,高階類型、不變對象、模式匹配、多重繼承,宏等等等等你們認為十分炫酷的功能,其他語言在一定時間之後很有可能會從別的語言抄過來。但唯有一樣,implicit,個人覺得這是 scala 的核心競爭力所在,現有的語言應該不會有哪個會二到把 scala 的 implicit 抄過去。scalaer 要找優越感,唯有在 implicit 這一大方向炫耀了。

說真的,implicit 是一個大方向,博大精深,並不是你們看到的擴展現有對象行為那麼簡單。我學了三年不敢說完全懂 implicit,尤其是 shapeless 那堆,看見代碼我就想逃。

implicit function,implicit variable,implicit parameter,implicit class,implicit macro,implicit function types(dotty)(以上是日常用到的 implicit 模式,只是根據印象羅列了一下,有些具體命名沒有去考究,歡迎大家指正)


Scala正如其名,一門可擴展的語言,它就是一個含有精美工具的工具箱,裡面有靜態類型, OOP, FP, 宏等工具。

我想這是馬丁設計的初衷,也是他寫的書上所說的,Scala不是在單純的混合面向對象和函數式,它是一門可被無限擴展的語言,每次添加新特性都是在豐富工具箱,所以有人說Scala太複雜了,也有人說Scala滿足了我對編程語言的所有幻想


Scala提供了幾乎你能想到的所有編程語言特性:

快速實驗: Scala有互動式命令行(REPL), 可以在上面快速的試各種語法和代碼。這對學習新特性,或者實驗新想法非常有用。(第1章)

一致性: 儘管Scala融合了靜態類型系統、面向對象、函數式編程等語言特性,但卻很少能看出融合的痕迹。Scala是我見到融合最多語言特性而又不顯得雜亂的編程語言之一。

類型安全:Scala創始人是教授,他先帶領創建了Java 5編譯器,而後覺得Java有太多羈絆而發明了Scala。 Scala編譯器和類型系統非常強大,它的目標是盡量把軟體錯誤消滅在編寫過程中。 Scala類型系統是圖靈完備的,甚至可以在編譯期間解決問題。

面向對象: Scala是面向對象的編程語言,所有的變數和方法都封裝在對象中,可以把信息封裝起來供外部使用。(第2章)

函數式編程:Scala同時又是函數式編程語言,函數可以獨立存在,可以定義一個函數作為另一個函數的返回值,也可以接受函數作為函數的參數。這給組合函數帶來了很大的便利。如何把面向對象編程形容成搭積木的話,函數式編程就像拼線條,更靈活和更有創意。(第3章)

非同步編程: 由於函數式編程提倡變數不可變,使非同步編程變得非常容易。同時Scala提供的Future(第5章), 和akka類庫(第9-11章),使得非同步編程變得非常容易。

基於JVM: Scala會被編譯成為jvm bytecode,所以Scala能無縫集成已有的Java類庫。你可以非常自然的使用已經存在的非常龐大且穩定的Java類庫,比如小巧好用的apache.common.*, 或者Java上的各種工具類庫。

因為如此眾多特性,用Scala可以優雅地編寫簡潔的代碼,同時又能減少很多低級錯誤;能快速進行開發,又能保證系統性能、團隊協作和長期維護。

此圖只說明了Scala高階函數的優勢,Java 8已經擁有部分函數式的能力。

orders.flatMap(o -&> o.products)


融合了函數式和面向對象編程,實用。

想學的話推薦 Scala 作者 Martin Odersky 的課:Functional Programming Principles in Scala | Coursera.org


Scala適合科研,Java適合工程。

Scala在Spark上可以很快做數據挖掘,效率非常高,但這個語言過於靈活,backward compatibility很差,不適合作為大型工程的基礎語言。領英的朋友說:他們每做一個新feature,就要花更多時間去理解以前的Scala code,現在只好花力氣把所有Scala都去掉。看看推特十幾年沒變,早前花力氣從Ruby改成Java/Scala,現在老總又說如果從頭再來就不會再選Scala了,工程角度來說,Scala不適合集體合作。

以下摘自wiki:

In March 2015, former VP of the Platform Engineering group at Twitter Raffi Krikorian, stated he would not have chosen Scala in 2011 due to its learning curve. The same month, LinkedIn SVP Kevin Scott stated their decision to "minimize [their] dependence on Scala." In November 2011, Yammer moved away from Scala for reasons that included the learning curve for new team members and incompatibility from one version of the Scala compiler to the next.


強大略顯複雜

編譯緩慢難忍

受益於jvm,受制於jvm

好壞摻半吧


優點:Functional + OO, strict typed; higher kind; excellent success records: akka, play2, slick3, scala.js, spark...; Will be easy express your idea once you master the key concepts, such as implicity and so on; Base on JVM, and able leverage its resources; Able write JS with scala too, and the running speed is ~1x same as raw JS; Tasty(future platform layer between scalac classes files, under developing);

缺點:Cannot develop OS or the bare metal related systems.


這麼說吧,java的缺點即是scala的優點,

反之:java的優點即是scala的缺點。


補充一個:

非常完善的Collection實現,這也是我目前見過最好用的Collection實現。如果你用過Spark的話,你會發現Spark的RDD其實是一種特殊的Collection。

Scala的collection整體設計都擁有非常一致的模式(包括用於非同步的Future),即便你使用第三方的擴展,也大多不會脫離這個模式。它包含了map/reduce,也包含了Monad。在使用scala的時候,我甚至不需要去查api文檔,我總是可以很準確的猜測出它會有什麼api提供,然後愉快的使用代碼補全。


面向對象與函數式編程的混合語言,參考了各種不同語言里的成分

如同http://docs.scala-lang.org/style/overview.html描述的

you should use Java』s naming conventions for classes and methods, but SML』s conventions for type annotation, Haskell』s conventions for type parameter naming (except upper-case rather than lower) and Ruby』s conventions for non-boolean accessor methods. Scala really is a hybrid language!

優勢是調用java庫很方便(被java調用其實不是很方便)

函數式寫法很舒服,掌握了scala基本上不願再回到java了

缺點是多範式導致風格不統一,學起來比較難,我個人認為比haskell還難學


Scala的類型系統讓我很煩,譬如

&:13: error: method with dependent type (t: T)t.A cannot be converted to function value

昨天我看到這個報錯,內心是崩潰的

而且函數不能攜帶泛型參數,這個限制也有點大。

最讓我煩不過的就是隱式參數了,個人認為隱式參數設計的不夠優雅,因為會修改方法的簽名,如果能以其他手段來代替就好了。


由於有&<-, =&>的存在,在代碼里看到&<=的時候我花了很長時間去回憶這個運算符是什麼意思……

最後靠搜索引擎才想起來是小於等於……


給你看個視頻,是位印度佬講Scala入門的,應該蠻基礎又蠻全面的,適合java程序員~https://www.youtube.com/watch?v=grvvKURwGNg


以下依次C++,Python,Scala

圖一:C++

圖二:Python

圖三:Scala

C++: 可手動操作的地方多。

Python:上手快,簡單

Scala:In short, elegant.

Verbose answer:語法簡潔,既可以REPL,也可以Build and Run。Type Safe的同時又可以Inference。支持Functional,OO。語法糖很酷。


stackoverflow上有關於Scala vs. Groovy vs. Clojure 三門語言的區別,這裡有對應的中譯版:Scala,Groovy,Clojure三門語言的區別。

另外,如果是新手希望快速入門Scala,建議從閱讀官方文檔入手:Introduction - Scala Documentation,這裡有對應的中文翻譯版本:Scala之旅-簡介


分頁阅读: 1 2