【51CTO精選譯文】幾個月前,我開始使用 Scala。我用過的編程語言還有 Pascal、C、C++、Java、PHP、Ruby 和 Groovy,但是,與所有那些我用過的語言相比,我發覺 Scala 是一門與眾不同的語言。我是在看到 Twitter 上關于 Ruby 和 Scala 討論之后,才開始我的 Scala 編程之旅的?,F在,使用 Scala 編程已經幾個月了,關于 Scala 我有兩點想法,雖然類似的想法已廣為人知,但我仍很想與你們分享:
51CTO編輯推薦:
Scala編程語言專題
◆Scala 的確很棒。
◆我的確認為計算機學院應該開一門 Scala 的語言課程。
在這篇文章中,我會講述為什么我會有這樣的想法,在此之前,有幾點我想要先聲明一下:
本文無意對編程語言進行評比,我要講述的主體是為什么你應該學習 Scala。
51CTO之前曾發布過一篇 Java 程序員為何要學習Scala的文章,可能也會對你有所幫助。
目前 Scala 有兩個實現方式,一種是在 JVM(Java 虛擬機)上運行,另一種是在 CLR(Common Language Runtime 的縮寫,即公共語言運行庫)上運行。不過,JVM 的實現方式更為成熟。如果你想要使用 .Net framework 框架,我認為最好還是聽從 Lift framework 框架創始人大衛·波拉克(David Pollack)的建議:使用 F#。但在這篇文章中,我將只關注 JVM 這種實現方式。
我是一個 Ruby 程序員,并且我會繼續喜歡 Ruby,因為它是我見到過的最棒的動態語言。但我也喜歡 Scala,因為在其他工作領域,它提供的某些功能非常強大。
現在,讓我們來仔細分析一下,是哪些原因讓我選擇 Scala 作為我的下一個編程語言:
強大的編程語言
Scala 是一門非常強大的語言,它允許用戶使用命令和函數范式進行編寫代碼,因此,編程時你可以使用常用的命令式語句,就像我們使用 C、Java、PHP 以及很多其他語言一樣,而且,你也可以使用類似 Lisp 語言中函數式語句,還有,你可以混合使用這兩種風格的語句,就像 Ruby 或 Groovy。
不過,當我們談論的函數范式時,與 Ruby 和 Groovy 有一點不同的地方,那就是 Scala 幾乎支持函數語言中所有已知的功能,比如,模式匹配(Pattern matching)、延遲初始化(Lazy initialization)、偏函數(Partial Function)、不變性(Immutability),等等...即是說,認識到這樣一個事實是非常重要的:Scala 的強大源自它對函數范式的支持,而后者令 Scala 成為一種高等級(high-level)的編程語言。對于高等級的編程語言,你只需關注 what(做什么)而不是如何做(how)。
下面,讓我們看一個 Java 示例:
int[] x = {1,2,3,4,5,6};
ArrayList res = new ArrayList();
for (int v : x) {
if (v % 2 == 1) res.add(new Integer(v));
}
仔細看一下上面這段示例代碼,你會注意到,我想要做的“what”部分(過濾掉奇數值)僅出現在第四行中,而其余行則是“how”如何做的部分(結果變量的初始化以及一個循環操作)。如果我想要再寫一個過濾器,用于篩選偶數值,那就需要再寫五行代碼,而使用一門像 Scala 這樣的高等級語言,你只需編寫“what”那部分的代碼:
val x = Array(1,2,3,4,5,6)
val res = x filter ( _ % 2 == 1 ) //過濾奇數值
val res2 = x filter ( _ % 2 == 0 ) //過濾偶數值
我們可以看到,相對于上文中的 Java 代碼段,這段代碼更加簡潔,而且具有更好的可讀性。
高效
Scala 是一種高效的編程語言,實際上,根據最新的 benchmark 性能測試,它幾乎和 Java 一樣快捷。在 JVM 上實現的 Scala 代碼,可以編譯為字節碼,在這一過程中,代碼通過優化階段進行編譯。尾遞歸優化是一個很好的示例,它可幫助用戶專注于函數范式而無需以犧牲性能為代價。還有一個示例是,將 Scala 值類型對象轉換為 Java 基本類型時進行的優化。
可擴展
Scala 語言本身的名字 Scala 來自 Scalable(可擴展的)一詞,這意味著這種語言可以按照用戶的需求進行擴展。因此,從根本上來講,用戶可以添加新的類型和控制結構。比如,我想要添加一個簡單的“loop”控制結構:
// 一個簡單的構建
def loop(range: Range)(op: Int=> Unit) {
range foreach (op)
}
loop(1 to 5 ){println} // 1 2 3 4 5
loop(1 to 5 ){x => if(x % 2 == 0) println(x)} // 2 4
還有幾個更為復雜的例子,Actor lib,它是作為擴展被添加到 Scala 這一語言中的,我們將在下文中對它展開討論。
不過,Scala 之所以是可擴展的,在于互相關聯的兩點:它是真正的面向對象的語言和真正的函數式語言。
面向對象
Scala 中每個事物都是對象(對象的方法除外),因此,沒有必要對基本(primitive)類型或引用類型進行區分,這就是所謂的:統一對象模型(Uniform Object Model)。但是,正如我之前在優化流程中所提到的,值類型對象被轉換為 Java 基本類型,因此不必擔心性能的問題。其內部還包含為類方法分組的單件對象(Singleton object)。
◆所有操作都是方法調用,+ - * ! / 都是方法,因此,沒有必要進行操作符重載。
◆非常精細的訪問控制,用戶可以控制對某些包的某些方法的訪問。
◆Scala 具有 trait,與 Ruby 中的 mixin 類似,就像 Java 中的 interfaces,但實現了某些它們的方法,因此,用戶在箱體(box)之外擁有富封裝器(wrapper)和富交互接口(interface)。
函數式語言
函數式語言具有很多特點,不過在擴展性這一語境中,我們所關心的是兩個事實:
◆函數是第一等級(first-class)的值
這表示用戶可以將函數作為值傳遞,也可以作為值返回。這樣可以獲得簡潔而具有可讀性的代碼,正如上文中作為示例的過濾代碼段。
◆純函數(pure function)
Scala 支持沒有副作用的純函數,這意味著:如果你的輸入相同,那么輸出結果也總是相同。這樣能夠讓代碼更為安全,對代碼測試也更為方便。
但是,Scala 是通過什么方式來支持純函數的呢?通過不變性(immutability):偏向固定的引用(與 java 中的 final 或其他語言中的 constant 類似)以及具有不變的數據結構,一旦創建便不可修改。
不變性是擁有純函數的安全保證,但并不是唯一的方式。沒有不變性,你仍然可以編寫安全的代碼。這就是為什么 Scala 不是強制推行不變性而只是鼓勵使用它。最終,你會發現 Scala 中許多數據結構具有了兩種實現方式,一種是可變的,另一種是不可變的,不可變的數據結構是缺省導入的。
每當提到不變性時,有人就會開始擔心性能的問題,對于某些情況,這種擔憂并非毫無來由,但對于 Scala,最終結果卻與這一擔憂相反。不可變的數據結構相對于可變的數據結構,更有助于獲得較高的效率。其原因之一在于強大的垃圾收集器(garbage collector),與 JVM 中的垃圾收集器類似。
更佳的并行模型
當涉及到線程這一問題時,Scala 支持傳統的 shared data 模型。但是,使用這種模型較長一段時間之后,許多人發現使用這種模型編寫代碼,非常難以實現以及進行測試。你總是需要考慮死鎖問題和競爭條件。因此,Scala 提供了另一個稱為 Actor 的并行模型,其中,actor 通過它的收件箱來發送和接收非同步信息,而不是共享數據。這種方式被稱為:shared nothing 模型。一旦你不再顧慮共享數據的問題,也就不必再為代碼同步和死鎖問題而頭痛。
被發送信息的不變性本質以及 actor 中串行處理,這兩者使得對于并行的支持更為簡便。
有關 Scala 并行的問題,請參閱這篇文章,對于這個概念你會有更好的理解。
在講述下一個要點之前,我需要提到這樣一個事實,一些人將 Actor 的使用視為編程語言的一種進化。正如,Java 的出現,將程序員們從指針和內存管理的泥淖中拯救出來一樣,Scala 的到來,讓程序員們不必再為代碼同步以及共享數據模型整天苦思冥想。
靜態類型
當我想要講述這一要點的時候,才發現,對于靜態類型語言的正反兩面,我試圖給予同樣的關注。事實上,關于這一話題的爭論總是沒完沒了,但我要作出兩點總結,而這兩點是大多數人討論的熱點:
◆使用靜態類型語言編寫的代碼更加健壯(robust)
TDD 的存在,讓許多關于動態類型語言和健壯代碼的討論失去了意義,雖然這是正確的,當我們仍然不能忽視這樣一個事實:對于動態類型語言,你需要編寫更多的測試代碼來檢查類型,而在靜態類型語言中,你可以將這些問題交給編譯器處理。此外,還有一些人認為,使用靜態類型語言,你的代碼將具有更好的自我記錄。
◆使用靜態類型語言編寫的代碼過于嚴格和冗長
像我這樣的動態類型語言的粉絲,認為通過鴨子類型(duck typing)可以寫出更具動態性的代碼結構。但同時他們還會抱怨,靜態類型語言導致代碼冗長。
關于靜態類型與動態類型的爭論,在51CTO之前發布的這篇文章中可以看到更多信息。
作為靜態類型語言,Scala 具有第一條中提到的優點,但是,第二點呢?
Scala 具有一個靈活的類型系統,并且可能是這一類型中最好的。很多情況下,如果你沒有指定類型,這一系統將能夠對類型進行推斷。
例如,你可以這樣編寫代碼:
val list: List[String] = List("one", "two", "three")
//list: List[String] = List(one, two, three)
val s: String = "Hello World!"
//s: java.lang.String = hello world!
```
但你也可以這樣編寫代碼:
```
val list = List("one", "two", "three")
//list: List[String] = List(one, two, three)
val s = "Hello World!"
//s: java.lang.String = hello world!
```
非常好,無論如何,它解決了代碼冗長的問題。但像鴨子類型(duck typing)那樣的問題,會怎樣呢?
答案還是:Scala 的類型系統具有的某些靈活性,可以讓你編寫如下的代碼:
```
def eat[T <: Animal](a: T) // whatever
```
其中,我們將類型 T 定義為 Animal 的子類型。還可以更加靈活:
```
def eat[T <: {def eat(): Unit}](a: T) // whatever
```
其中,我們將類型 T 定義為一個具有非法 eat 的類型。
事實上,Scala 的類型系統非常豐富,你可以在這里找到更多信息。
**模式匹配**
我必須坦白,在猶豫良久之后,我才決定寫一寫 Scala 的這一特點。事實上,我本來沒有打算討論 Scala 的函數功能,但看到一篇有關對象分支(switch)應用的文章后,我想,還是有必要聊聊這個特點。以下內容基本上都來自這篇博客文章:
模式匹配究竟是用來做什么的?它讓你可以將一個值對多種情況(case)進行匹配,有點類似 Java 中的分支(switch)語句。但它不是僅僅匹配數字(這是分支語句的作用),而是用戶能夠對本質上為對象創建形式(creation form)的事物進行匹配。
以下示例也來自上文提到的博客:
```
x match {
case Address(Name(first, last), street, city, state, zip) => println(last + ", " + zip)
case _ => println("not an address") // 缺省情況
}
```
對于第一種情況,模式 Name(first, last) 嵌套在模式 Address(…) 中。 其中的 last 值,被傳遞到 Name 構造函數,然后進行提取,因此在箭頭右側的表達式中是可用的。
***模式匹配的意義***
*為什么你需要模式匹配?我們都會有復雜的數據。如果我們堅持嚴格的面向對象編程,那么我們就不愿去關注數據樹的內部情況。相反,我們想要調用方法,讓方法來做這些事情。如果我們有能力完成這件事,就不會非常需要模式匹配,因為方法滿足了我們的要求。但是,很多時候對象沒有我們所需的方法,并且我們不能(或不愿)為對象添加新的方法。*
因此,模式匹配被認為是一種獲得擴張性的有效方法,并且,它還為該問題提供了一種不錯的解決方案,訪問者設計模式所導致的冗長除外。
不管怎樣,強烈推薦你看看上面所提到的文章中”擴展性的兩個方向“(Two directions of extensibility)那個小節。
**簡單的 DSL(特定領域語言)**
編寫 DSL,Scala 是一個很好的選擇。事實上,Scala 適用于內部和外部 DSL。在這篇文章中,你可以找到一些使用 Ruby 和 Scala 編寫內部 DSL 的特點比較。下面這篇文章也很棒,是關于使用 Scala 編寫內部 DSL 的:Boolean Algebra Internal DSL in Scala (aka fun with Unicode names )。
此外,對于外部 DSL,Scala 也應該是首選語言,背后的原因是解析器組合子庫(parser combinator lib),它讓為新語言編寫編譯器成為一件很酷的事。
**與 Java 代碼之間的互操作性**
在 JVM 上的實現 Scala 的程序可以無縫地與 Java 平臺整合,很多 Scala 類型實際上都編譯為 Java 類型,因此,用戶可以放心地使用 Java 類型。而且,你也可以混合地使用 JVM 語言來編程,如:JRuby、Groovy、Clojure 等。這里有一篇不錯的文章,提供了這種示例。
**學習型語言**
我有兩個習慣,在 Scala 的學習過程中,我堅持了這兩個習慣:
◆遇到新的技術術語,訪問維基百科,理解更多信息;比如 Function literal(文本函數)、Referentially transparent(引用透明度)、Partial function(偏函數)、Currying(科里華),還有很多其他術語。
◆參考我對其他語言的理解,檢查這些術語的涵義是否實現。
通過一些好的練習,如編寫沒有副作用的純函數,將精力集中在代碼中的“what”部分,而將“how”的部分交給語言處理;這兩個習慣讓我獲得更多知識,也提高了代碼的質量。
**團隊**
Scala 由馬丁·奧德斯基(Martin Odersky)設計,他是瑞士聯邦理工學院洛桑分校(EPFL)編程方法實驗室小組的管理者。奧德斯基曾受雇于 Sun 公司編寫 Java 1.1 編譯器,他還是 Java 1.1 到 Java 1.4 的 Javac 主要開發者。此外,他還是 Java Generics 的提出者。51CTO編輯曾通過電子郵件與奧德斯基就 Scala 的語言特性進行了交流,并得到了回復信件[如下](http://developer.51cto.com/art/200906/128037.htm)。
Scala 現在由奧德斯基和他在瑞士聯邦理工學院洛桑分校的團隊維護。不過,還有其他一些具有才華的開發者參與,通過多年的工作,他們共同打造出了 Scala 這一編程語言。
**來源**
Scala 受到了[多種語言的啟發](http://developer.51cto.com/art/200907/134890.htm):
◆大多數的句法來自 Java 和 C#。
◆其他一些元素也來自 Java,比如:基本類型(basic type)、類庫,以及其運行模型。
◆它所用的統一對象模型是由 Smalltalk 最先使用的。
◆通用嵌套(universal nesting)的理念也出現在 Algol、Simula 中,而且最近還出現在 Beta 和 gbeta 中。
◆函數式編碼的方法在精神上也與 ML 語言家族類似,該語言家族中包含 SML、OCaml,以及最主要的成員 [F#](http://developer.51cto.com/art/200906/130786.htm)。
◆Scala 標準庫中的許多較高階的函數,也出現在 ML 和 Haskell 中。
◆Scala 的隱式參數也是受到 Haskell 類型類的啟發。Scala 基于 actor 的并行庫主要是受到 Erlang 的啟發。
◆將插入(infix)操作符視為函數,以及允許文本函數(或 block 區塊)作為參數,以使庫能夠定義控制結構,這些特定的理念可以回溯至 Iswim 和Smalltalk。
**專家觀點**
實際上,像詹姆士·斯特拉坎(James Strachan:編程語言 Groovy 的創始人)的[這樣的言語](http://developer.51cto.com/art/200907/134785.htm)讓人感到有點驚喜:
說實話,如果有人在 2003 年給我一本由馬丁·奧德斯基(Martin Odersky)、萊克斯·斯彭(Lex Spoon)和比爾·文納斯(Bill Venners)合著的《Programming in Scala》,我很可能不會再去創建 Groovy。
在結束之前,我做一下總結:
我喜歡 Scala,因為它是高效的、學習型的語言,具有較好的并行模型,以及非常適用于編寫 DSL。
原文:Scala is my next choice
作者:khelll 譯者:[司馬牽牛](http://tsaizb.blog.51cto.com/)
【相關閱讀】
[Scala取代Java?可能嗎?熱議仍持續不斷
](http://developer.51cto.com/art/200907/135770.htm)
[Groovy創始人:Java面臨終結 Scala將取而代之
](http://developer.51cto.com/art/200907/134785.htm)
[Scala如何改變了我的編程風格:從命令式到函數式
](http://developer.51cto.com/art/200906/127825.htm)
[Scala的類型系統 比Java更靈活
](http://developer.51cto.com/art/200906/126710.htm)
[Java程序員,你為什么要關注Scala
](http://developer.51cto.com/art/200905/125526.htm)