爲什麼選用 Scala

爲什麼選用 Scala
    當今的企業和互聯網應用程序必須平衡一系列的要點。它們要有快速而可靠的實現。新的功能要能在短時間內通過週期漸進的方式加入。除了簡單地提供商務邏輯之外,應用程序必須支持訪問安全控制,數據持久化,事務,和其它的進階功能。程序必須高度易用和可擴展,同時要求支持併發和分佈式計算。應用程序會被網絡化,並且提供人和機器都易於使用的接口。
    要達成這些挑戰,許多軟件開發者在尋找新型的編程序言和工具。以往備受推崇的如:Java,C#,和C++已經不再是開發這些次世代應用程序的最佳候選。
    如果你是一個Java 程序開發者
    Java 是由Sun 公司在1995 年,互聯網高速發展的時候正式引入的。 由於當時需要一個安全的,可移植的,開發者友好的程序語言,它被迅速地推崇爲編寫瀏覽器應用的理想語言。而當時的主流語言,C++,則並不適合這個領域。
    今天,Java 被更多地使用在服務器端程序中。它是開發網絡和企業應用的最流行的語言之一。
    然而,Java 是它們那個時代的產物,至今也有一定年代了。在1995年,Java 爲了拉攏C++開發者,提供了和C++ 足夠相似的語法,同時也避開了C++ 語言上的缺陷和危險。Java 採納了絕大多數那個時代對解決軟件開發問題有用的概念,比如面向對象編程(OOP), 同時也丟棄了一些麻煩的充滿問題的技術,比如人工的內存控制。這些設計決策在最小化複雜度和最大化開發生產力上達到了一個優異的平衡。然而,從Java 出生演化到現在,許多人認爲它變得越來越複雜,而且並沒有顯著地解決新的程序開發過程中面臨的問題和挑戰。
    程序開發者想要一種更精煉和更靈活的語言去提高他們的生產效率。這也是如今所謂的Ruby ,Python 這樣的腳本(Script)語言大行其道的原因之一。
    永無休止的需求驅動着架構向大規模併發開發進行。然而,Java 的併發模型是基於對共享的,可變的信號狀態的同步存取,從而導致了複雜的,易錯的程序。
    當Java 漸漸老化時,運行它的 Java 虛擬機(JVM)卻持續地散發着光芒。如今JVM 的性能優化是非凡的,它允許字節碼(byte code)在許多情況下得到和本地編譯的代碼相同的性能。今天,許多程序開發者相信使用基於JVM 的新語言是正確的前進道路。Sun 顯然是擁護這個趨勢的,他們僱傭了JRuby 和Jython (Ruby 和Python 在JVM 上的實現)的主要開發者。
    Scala 的出現對於Java 開發者來說提供了一個更加新式的語言。同時保留了JVM 的驚人的性能和開發了十幾年的Java 庫的寶貴財富。
    如果你是一個Ruby,Python 的開發者
    像Ruby,Python,Groovy,JavaScript,和Smalltalk 這樣的動態類型語言,通常因爲它們優雅的靈活性,強大的元編程能力(metaprogramming),提供了很高的生產力。
    如果撇開它們在高產能上的優勢,動態語言也許不是個萬金油,特別對於大規模和高性能程序來說,不是最佳選擇。在編程社區裏,有一個對於動態類型和靜態類型究竟誰更佔有優勢進行的冗長爭論。很多的比較觀點多少都有些主觀。我們不會在這裏討論所有的這些爭論,但是我們會提供一些對此的看法以供參考。
    相比靜態語言來說,優化動態語言的性能更富有挑戰性。在靜態語言中,優化器可以根據類型信息來進行決策。而在動態語言中,只有很有限的信息是可用的,這使得優化器的選擇更加困難。雖然近年來在動態語言優化方面的提升漸漸浮現希望,但是它們還是落在靜態語言的優化藝術的後面。所以,如果你確實需要很高的性能,靜態語言或許是一個更保險的選擇。
    靜態語言同樣可以使開發過程獲得好處。集成開發環境(IDE)的一些功能,比如自動完成(有時候被稱爲智能感知)在靜態語言中更容易完成,因爲那些類型信息都是可用的。而更加明顯的類型信息在靜態代碼中促進了代碼的自我解釋,隨着項目的發展,這對於開發者意圖的互相交流是十分重要的。
    當使用一種靜態語言時,你必須時刻考慮使用恰當的類型。這迫使你在選擇設計時更加小心。這雖然會拖慢日常的設計決策,但是長此以往,在應用程序中對類型使用的思考會帶來更爲清晰的設計。
    靜態語言的另外一個小的好處就是編譯時期的額外檢查。我們通常認爲這個優勢被誇大了,因爲類型不匹配的錯誤只是日常見到的運行時錯誤中的一小部分。編譯器無法發現邏輯錯誤,這顯然更加重要。只有一個綜合的,自動的測試組可以發現邏輯錯誤。對於動態語言來說,測試也必須覆蓋可能的類型錯誤。如果你以前編寫過動態類型語言,你會發現你的測試組其實會小一些,但不會小很多。
    許多開發者發現靜態語言太過冗長,抱怨靜態類型是冗長的元兇,而事實上真正的原因是缺少類型推斷。在類型推斷的情況下,編譯器會根據上下文推斷值的類型。例如,編譯器會識別在 x = 1 + 3 中x 是一個整型。類型推斷能顯著地減少代碼的長度,使得代碼更像是用動態語言編寫出來的。
    我們都曾經在不同的時間和靜態語言和動態語言打過交道。我們發現兩種類型的語言都因爲不同的原因被廣爲關注。我們相信現代軟件開發者必須掌握一系列的語言和工具。有時,動態語言會是完成工作的正確工具;而有時,一個靜態語言,例如Scala,會是你正需要的。
    Scala 介紹
    Scala 是一種迎合現代軟件開發者需求的語言。它是靜態的,混合範式的(mixed-paradigm),基於JVM 的語言;它在擁有簡潔,優雅,靈活的語法的同時,也提供了一個久經考驗的類型系統和慣用語法,所以從小巧的解釋性腳本到大型的複雜系統它都可以勝任。那可是一大口蛋糕,所以,讓我們詳細地來了解下它的一些特性。
    靜態類型
    正如我們在前面的章節所描述的,一個靜態類型的語言在一個變量的生命週期內都會綁定一個類型。相反的,動態類型的語言則是根據變量所引用的值來綁定類型,這意味着變量的類型可以隨着它引用的值改變而改變。
    在最新的基於JVM 的語言中,Scala 是爲數不多的靜態類型語言,而且是最出名的一個。
    混合範式 - 面向對象編程
    Scala 完全支持面向對象編程(OOP)。Scala 在改進Java 對OOP 的支持的同時,添加了traits (特性)的概念,它可以簡潔地實現類之間的混合關係。Scala 的traits 和Ruby 的modules (模塊)概念類似。如果你是一個Java 開發者,可以把traits 想象成interfaces (接口)和implementations (實現)的統一體。
    在Scala 中,所有的東西實際上都是一個object (對象)。Scala 不像Java,它沒有原始類型(元數據類型)。相反的,所有的數值類型都是正真的objects。 然而,爲了優化性能,Scala 會實時地在底層實現中使用原始類型。另外,Scala 不支持static (靜態)或者class-level members (類級別成員)的類型,因爲它們並沒有和一個實例(instance)關聯。相反,Scala 支持單例模式,可以應用於那些一種類型只有一個實例的情況。
    混合範式 - 函數式編程
    Scala 完全支持函數式編程(FP)。FP 是一種比OOP 更加古老的編程範式,它被學術界的象牙塔庇護至今。FP 因爲簡化了某些設計,尤其是併發上的問題而受到了越來越多的關注。“純粹”的函數式語言不允許任何易變狀態(mutable state),因而避免了對易變狀態的同步和共享訪問。相反的,用純函數式語言編寫的程序在併發自主的進程中通過傳遞消息來通信。Scala 通過Actors 庫來支持這種模式,但是它同時允許mutable (易變的)和immutable (不易變的)變量。
    函數是FP 的一類公民,這意味着它們可以被賦值給變量,被傳遞給其他函數等,就像普通的值一樣。這個特色允許通過元操作來組合一些高級行爲。因爲Scala 遵守所有的東西都是object 的原則,函數在Scala 中也是objects。
    Scala 同時支持閉包,一種動態語言比如Python 和Ruby 從函數式編程世界中引用過來的特性。Java 很遺憾地沒有在最近的版本中包含這個特性。閉包本質上是一個函數和其引用的變量的統一定義。這些變量不作爲傳入參數或者函數內的局部變量。一個閉包封閉了這些引用,所以函數調用可以安全的引用這些變量,即使它們已經超出了函數的作用域。閉包是一個強大的抽象,以至於objects 系統和基礎控制結構經常是用它們實現的。
    一種同時有JVM 和。NET 實現的語言
    Scala 是衆所周知的基於JVM 的語言,這意味着Scala 會生成JVM 字節碼。一個能生成CLR 字節碼的基於。NET 的Scala 版本也同時在開發中。當我們提到底層的“運行時”時,我們通常是指JVM。但是許多我們提到的概念能同時運用於兩種不同的運行時。當我們討論有關JVM 的細節時,它們大致也能應用於。NET,除非我們特別說明。
    Scala 的編譯器使用了一些聰明的技巧來映射Scala 的擴展到相應的字節碼。在Scala 裏,我們可以輕鬆地調用產生自Java 源代碼(JVM)或者C# 源代碼(。NET)的字節碼。同樣的,你也可以在Java,C# 代碼裏調用Scala。運行在JVM 和CLR 上,允許Scala 開發者來充分利用現有的庫來和運行在這些運行時的其他語言交互。
    簡潔的,優雅的,靈活的語法
    Java 的語法實際上有些冗長。 Scala 使用了一些技巧來減少不必要的語法,使得Scala 源碼幾乎和其他的動態語言一樣簡潔。類型推斷使得顯式的類型聲明信息在大多數場合下減少到了最低。類型和函數的聲明變得非常簡潔。
    Scala 允許函數名字包含非字母數字的字符。結合一些語法上的技巧,這些特性允許用戶定義一些看起來像操作符的方法。這樣,在語言核心之外的庫對於用戶看來就會比較自然。
    複雜的類型系統
    Scala 擴展了Java 的類型系統,同時提供了更靈活的類型和一些高級的類型結構。這個類型系統起初開起來可能會有些恐怖,但是大多數時候你不用擔心這些高級的結構。類型推斷幫助你自動推斷類型簽名,所以用戶不用人工提供一般的類型信息。不過,當你需要它們的時候,高級類型特性可以給你提供更靈活的方式,用類型安全的方式解決設計上的問題。
    可伸縮性 - 架構
    Scala 被設計來服務從小的,解釋性腳本到大型的,分佈式系統。Scala 提供了4 種語言機制來提供更靈活的系統組合:1)顯式的自我類型(selftype),2)類型成員和種類的抽象,3)嵌套的類,以及4)使用traits 的混合結構。
    沒有其它的語言同時提供所有這些機制了。這些機制一起允許用一種類型安全和簡潔的方式來構建由可重用組件組成的程序。正如我們所見,許多常見的設計模式和架構技術,例如依賴注入模式,可以容易地用Scala 來實現,而不用冗長的樣板代碼或者XML 配置文件 -- 它們經常讓Java 開發變得很枯燥。
    可伸縮性 - 性能
    因爲Scala 代碼運行在JVM 和CLR 上,它能獲得所有來自這些運行時和支持性能靈活性調整的第三方工具的性能優化,例如分析器(profiler),分佈式緩存庫,集羣機制等。如果你相信Java 和C# 的性能,那麼你就能信任Scala 的性能。當然,一些特別的結構在這個語言環境下和某些庫中相比其它語言會運行地特別高效或者低效。一如既往的,你應該在必要的時候分析和優化你的代碼。
    表面上看起來OOP 和FP 像是不兼容的。但實際上,Scala 的設計哲學是OOP 和FP 應該協同合作而不是對立。其中一方的特性應該能增強另一方。
    在FP 裏,函數沒有副作用,變量都是不易變的。而在OOP 中,可變狀態和副作用都十分常見,甚至是被鼓勵的。Scala 讓你來選擇解決設計問題最佳的方式。函數式編程對於併發特別有用,因爲它摒棄了對易變狀態的同步訪問。然而,“純”函數式語言是十分嚴格的。有一些設計問題還是使用易變對象比較容易解決。
    Scala 的名字來自詞語:可伸縮的語言(scalable language)的縮寫。這就建議Scala 的發音爲scale-ah,實際上Scala 的創建者發音爲scah-lah,像意大利語中的“stairs”(樓梯)。也就是說,兩個“a 的”的發音是一樣的。
    Martin Oderskey  的在計算機語言方面的背景和經驗是顯著的。在你學習Scala 的時候,你會了解這是一個仔細考慮了設計決策,利用了所有類型理論,OOP 和FP 的藝術的語言。Martin 在JVM 方面的經驗對於Scala 和JVM 平臺的優雅結合有着顯著的幫助。它綜合了OOP 和FP 的優點,是一個優秀的兩全其美的解決方案。
    Scala 的誘惑
    今天,我們的產業幸運地擁有許多語言方面的選擇。動態語言的能力,靈活性,優雅已經使它們十分流行。但是,Java 和。NET 庫,已經JVM 和CLR 的性能作爲珍貴的寶藏,符合了許多實際的企業和互聯網項目的需求。
    Scala 引起衆人的興趣是因爲它的簡潔語法和類型推斷,看起來像動態腳本語言。但是,Scala 提供了所有靜態類型的優勢,一個現代的對象模型,函數式編程,和先進的類型系統。這些工具允許你建立一個可伸縮的,模塊化的應用程序,並且重用之前的Java 和。NET API, 充分利用JVM 和CLR 的性能。
    Scala 是面向專業開發者的語言。相比較與Java 和Ruby,Scala 更難掌握。因爲它要求OOP,FP 和靜態類型方面的技能,這樣才能更高效地使用它。它誘使我們偏愛動態語言的相對簡潔。但是,這種簡潔是一種假象。在一種動態類型語言中,使用元編程特性來實現高級設計經常是必要的。元編程十分強大,但是使用它需要經驗,而且會導致代碼變得難以理解,維護和調試。在Scala 中,許多類似的設計目標可以通過類型安全的方式來達到,利用它的類型系統和通過traits 實現的混合結構。
    我們覺得在Scala 的日常使用中所需求的額外努力會迫使我們在設計時更加謹慎。久而久之,這樣的機率會導致更加清晰的,模塊化的,可維護的系統。幸運的是,你不必所有時候都去追逐Scala 所有複雜的功能。你的大多數代碼會簡單清晰,就像是用你最喜歡的動態語言寫出來的一樣。
    另外一個策略是聯合幾種簡單的語言,比如Java 來做面向對象的代碼,Erlang 來做函數式,併發的代碼。這樣一個分解會工作的很好,如果你的系統能清晰地分解成這些不想關聯的部分,並且你的團隊能掌控這樣一個混雜的環境。Scala 對於那些僅需要一個全能語言的情況是最具吸引力的。也就是說,Scala 代碼可以和諧地與其他語言共處,尤其是基於JVM 和。NET 的語言。
    安裝 Scala
    這個章節描述瞭如何安裝Scala 的命令行工具, 以便可以儘快讓Scala 跑起來,這也是運行本書所有範例的必要充分條件。在各種編輯器和集成開發環境(IDE)中使用Scala 的細節,請參見和IDE 集成,在第14章-Scala 工具,庫和IDE 支持。本書的範例是用Scala 版本2.7.5.final 來編寫和編譯, 也是本書在編寫時候的最新的發行版;也有部分是用Scala 版本2.8.0 的每夜編譯版本,當你讀到這本書的時候應該已經最終完成了。
    注意
    2.8 版本引入了很多新的特性,我們會在這本書中予以強調。
    我們在這本書中會選用JVM 版本的Scala。 首先,你必須安裝Java 1.4 或更高的版本(推薦1.5 或更高)。如果你需要安裝Java,請登錄,按照指示在你的電腦上安裝Java。
    Scala 的官方網站 。要安裝Scala,去到下載(downloads)頁面 。按照下載頁面上的指示下載適合你係統環境的安裝包。
    最簡單的跨平臺安裝包是IzPack 安裝器。下載Scala 的jar (譯註:編譯完以後的Java 專屬格式)文件,可以選擇scala-2.7.5.final-installer.jar 或者 scala-2.8.0.N-installer.jar, N在這裏是2.8.0 版本的最新發布版本。在終端窗口中(譯註:或者Windwos 下的命令行),定位到下載的目錄,使用java 命令來安裝Scala。假設你下載了scala-2.8.0.final-installer.jar,運行如下命令會帶領你完成安裝過程。
    java -jar scala-2.8.0.final-installer.jar  提示
    在蘋果系統下(Mac OS X),安裝Scala 的最簡單方式是使用MacPorts。按照這個頁面的安裝指示,然後使用 sudo port insall scala. 不用幾分鐘你就可以運行Scala 了。

  在本書中,我們會使用符號scala-home 來指定Scala 安裝路徑的根目錄。
    注意
    在Unix,Linux,和Mac OS X 系統下,你需要root 用戶權限,或者sudo 命令才能把Scala 安裝在一個系統目錄下。比如:
    scala-home = /usr/local/scala-2.8.0.final. 或者,你也可以下載並且展開壓縮過的tar 文件(比如scala-2.8.0.final.tgz)或者zip 文件(scala-2.8.0.final.zip)。在類Unix 系統中,展開壓縮文件到一個你選擇的路徑。然後,把scala-home/bin 子目錄加入到你的PATH 環境變量中。例如,如果你安裝到 /usr/local/scala-2.8.0.final,那麼把/usr/local/scala-2.8.0.final/bin 加入到PATH。
    要測試你的安裝,在命令行下運行如下命令:
    scala -version  我們會在後面學習如何使用scala 命令行。你應該能獲得如下輸出:
    Scala code runner version 2.8.0.final -- Copyright 2002-2009, LAMP/EPFL  當然,你看到的版本號會根據你安裝的版本而改變。從現在起,當我們展示命令行輸出時候如果包含版本號,我們會使用2.8.0.final。
    祝賀你,你已經安裝了Scala!如果你在運行scala 後獲得一個錯誤消息:command not found(無法找到命令),重新檢查你的PATH 環境變量,確保它被正確地設立,幷包含了正確的bin 目錄。
    注意
    Scala 2.7.X 以及之前的版本和JDK 1.4 以及更新的版本兼容。Scala 2.8 版本捨棄了1.4 的兼容性。注意Scala 會使用原生的JDK 類庫,比如String 類。在。NET 下,Scala 使用對應的。NET 類。
    同時,你應該能在那個下載頁面找到Scala API 文檔和源碼的下載鏈接。
    更多信息
    在探索Scala 的時候,你會在這個網頁上發現有用的資源。你會發現開發支持工具和庫的鏈接,還有教程,語言規範【ScalaSpec2009】,和描述語言特性的學術論文。
    Scala 工具和API 的文檔特別有用。你可以在這個頁面瀏覽API 文檔。這個文檔是使用scaladoc 工具生成的,類似於Java 的javadoc 工具。參見第14章- Scala 工具,庫和IDE支持的“scaladoc 命令行工具”來獲得更多的信息。
    你也可以在下面頁面下載一個API 文檔的壓縮文件來本地瀏覽。或者你可以用sbaz 工具來安裝,如下
    sbaz install scala-devel-docs sbaz 和scala,scalac 命令行工具安裝同樣的bin 目錄下。安裝的文檔也同樣包含了scala 工具集(包括sbaz)的細節和代碼示例。要獲取更多Scala 命令行工具和其他資源的信息,參見第14章- Scala 工具,庫和IDE 支持。
    初嘗 Scala
    是時候用一些實在的Scala 代碼來刺激一下你的慾望了。在下面的範例中,我們會描述足夠的細節讓你明白髮生了什麼。這一節的目標是給你一個大致概念,讓你知道用Scala 來編程是怎麼一回事。我們會在以後的各個章節來探索Scala 更多的特性。
    作爲第一個實例,你可以用兩種方式來運行它:交互式的,或者作爲一個“腳本”。
    讓我們從交互式模式開始。我們可以通過在命令行輸入scala,回車,來啓動scala 解釋器。你會看到如下輸出。(版本號可能會有所不同。)
    Welcome to Scala version 2.8.0.final (Java …)。  Type in expressions to have them evaluated.  Type :help for more information.  scala> 最後一行是等待你輸入的提示符。交互式的scala 命令對於實驗來說十分方便(參見《第14章-Scala工具,庫和IDE支持》的“Scala 命令行工具”章節,來獲取更多信息)。一個像這樣的交互式解釋器被稱爲REPL:讀(Read),評估(Evaluate),打印(Print),循環(Loop)
    輸入如下的兩行代碼。
    val book = "Programming Scala" println(book) 實際上的輸入和輸出看起來會像是這樣。
    scala> val book = "Programming Scala" book: java.lang.String = Programming Scala  scala> println(book)  Programming Scala  scala> 在第一行我們使用了val 關鍵字來聲明一個只讀變量 book。注意解釋器的輸出顯示了book 的類型和值。這對理解複雜的聲明會很方便。第二行打印出了book 的值 -- Programming Scala。
    提示
    在交互模式(REPL)模式下來測試scala 命令是學習Scala 細節的一個非常好的方式。
    這本書裏的許多例子可以像這樣在解釋器裏運行。然而,通常使用我們提到的第二個方式會更加方便:在文本編輯器中或者IDE 中編寫Scala 腳本,然後用同樣的scala 命令來執行它們。我們會在這章餘下的絕大多數部分使用這種方式。
    用你選擇的文本編輯器,保存下面例子中的Scala 代碼到一個名爲upper1-script.scala 的文件,放在你選擇的目錄中。
    // code-examples/IntroducingScala/upper1-script.scala  class Upper {    def upper(strings: String*): Seq[String] = {      strings.map((s:String) => s.toUpperCase())    }  }  val up = new Upper  Console.println(up.upper("A", "First", "Scala", "Program")) 這段Scala 腳本把一個字符串轉換到大寫。
    順便說一下,在第一行有一句註釋(在代碼例子中是源文件的名字)。Scala 使用和Java,C#,C++等一樣的註釋方式。一個// 註釋會影響整個一行,而/* 註釋 */ 方式則可以跨行。
    要運行這段腳本,打開命令行窗口,定位到對應目錄,然後運行如下命令。
    scala upper1-script.scala 文件會被解釋,這意味着它會被編譯和執行。你會獲得如下輸出:
    Array(A, FIRST, SCALA, PROGRAM) 解釋 VS 編譯,運行Scala 代碼
    總的來說,如果你在命令行輸入scala 而不輸入文件名參數,解釋器會運行在交互模式。你輸入的定義和語句會被立即執行。如果你附帶了一個scala 文件作爲命令參數,它會把文件作爲腳本編譯和運行,就像我們的 scala upper1-script.scala 例子一樣。最後,你可以單獨編譯scala 文件,運行class 文件,只要你有一個main 函數,就像你通常使用java 命令一樣。(我們會馬上給出一個例子)
    你需要理解有關使用解釋模式的侷限和單獨編譯運行之間的一些微妙的區別。我們會在《第14章- Scala 工具,庫和IDE 支持》的“命令行工具”部分詳細解釋這些區別。
    當我們提及執行一個腳本時,就是說用scala 命令運行一個Scala 源文件。
    在這個例子裏,類Upper (字面意思,沒有雙關) 裏的upper 函數把輸入字符串轉換爲大寫,然後作爲一個數組返回。最後一行把4個字符串轉換完以後輸出。
    爲了學習Scala 語法,讓我們來更詳細地解釋一下代碼。在這僅有的6行代碼裏面有許多細節!我們會解釋一下基礎的概念。這個例子的所有的概念會在這被書的後面幾個章節被詳細地講解。
    在這個例子裏,Upper 類以class 關鍵字開始。類的主體被概括在最外面的大括號中 {…}。
    upper 方法的定義在二行,以def 關鍵字開始,緊接着是方法名,參數列表,和方法的返回類型,最後是等於號“=”,和方法的主體。
    在括號裏的參數列表實際上是一個String(字符串)類型的可變長度參數列表,由冒號後面後面的String* 類型決定。也就是說,你可以傳入任意多的,以分號分隔的字符串(包括空的列表)。這些字符串會被存在一個名爲strings 的參數中。在這個方法裏面,strings 實際上是一個Array(數組)。
    注意
    當在代碼裏顯式地爲變量指定類型信息時,類型註解應該跟在變量名的冒號後面(也就是類Pascal 語法)。Scala 爲什麼不遵照Java 的慣例呢? 回想一下,類型信息在Scala 中經常是被推斷出來的(不像Java),這意味着我們並不總是需要顯式的聲明類型。和Java 的類型習慣比較,item: type 模式在你忽略掉冒號和類型註解的時候,更容易被編譯器清楚地分析。
    方法的返回類型在參數列表的最後出現。在這個例子裏,返回類型是Seq[String],Seq(sequence)是一種特殊的集合。它是參數化的類型(像Java 中的泛型),在這裏String 是參數。注意,Scala 使用方括號[…] 來指定參數類型,而Java 使用尖括號<…>。
    注意
    Scala 允許在方法名中使用尖括號,比如命名“小於”方法爲<,這很常見。所以,爲了避免二義性,Scala 使用了方括號來聲明參數類型。它們不能被用於方法名。這就是爲什麼Scala 不允許像Java 那樣的使用尖括號的習慣。
    upper 方法的主體跟在等於號“=”後面。爲什麼是一個等於號?爲什麼不像Java 一樣直接使用大括號{…} 呢?因爲分號,函數返回類型,方法參數列表,甚至大括號都經常會被省略,使用等於號可以避免幾種可能的二義性。使用等於號也提醒了我們,即使是函數,在Scala 裏面也是值。這和Scala 對函數是編程的支持是一致的。我們會在《第8章,Scala 函數式編程》裏討論更多的細節。
    函數的主體調用了strings 數組的map 方法,它接受一個字面函數(Function Literal)作爲參數。字面函數也就是“匿名”函數。它們類似於其它語言中的Lambda 表達式,閉包,塊,或者過程。在Java 裏,你可能會在這裏使用一個匿名內部類來實現一個接口(interface)定義的方法。
    在這個例子裏,我們傳入這樣的一個字面函數。
    (s:String) => s.toUpperCase() 它接受一個單獨的名爲s 的String 類型參數。 函數的主體在“箭頭” => 的後面。它調用了s 的toUpperCase() 方法。調用的結果會被函數返回。在Scala 中,函數的最後一個表達式就是返回值,儘管你也可以在其它地方使用return 語句。return 關鍵字在這裏是可選的,而且很少被用到,除非在一段代碼中間返回(比如在一個if 語句塊中)。
    注意
    最後一個表達式的值是默認的返回值。不需要顯式的return。
    繼續,map 把strings 裏面的每一個String 傳遞給字面函數,從而用這些返回值創建了一個新的集合。
    要運行這些代碼,我們創建一個新的Upper 實例,然後把它賦值給一個名爲up 的變量。和Java,C#,以及其它類似的語言一樣,語法new Upper 創建了一個新的實例。變量up 被val 關鍵字定義爲一個只讀的值。
    最後,我們對一個字符串列表調用upper 方法,然後用Console.println(…) 方法打印出來。這和Java 的System.out.println(…) 等效。
    實際上,我們可以更加簡化我們的代碼。來看下面這一段簡化版的腳本。
    // code-examples/IntroducingScala/upper2-script.scala   object Upper {    def upper(strings: String*) = strings.map(_.toUpperCase())  }  println(Upper.upper("A", "First", "Scala", "Program"))  這段代碼做了一模一樣的事情,但是用了更少的字符。
    在第一行,Upper 被定義爲一個object,也就是單體模式。實際上我們是定義了一個class,但是Scala 運行時僅會創建Upper 的一個實例。(比如,你就不能寫new Upper了。)Scala 的objects 被使用在其他語言需要“類級別”的成員的時候,比如Java 的statics (靜態成員)。我們實際上並不需要更多的實例,所以單體模式也不錯。
    注意
    Scala 爲什麼不支持statics?因爲在Scala 中,所有的東西都是一個object,object 結構使得這樣的政策保持了一致。Java 的static 方法和字段並不綁定到一個實際的實例。
    注意這樣的代碼是完全線程安全的。我們沒有定義任何會引起線程安全問題的變量。我們使用的API 方法也是線程安全的。所以,我們不需要多個實例。單體模式工作的很好。
    在第二行的upper 方法的實現也變簡單了。Scala 通常可以推斷出方法的返回值(但是方法參數的類型就不行了),所以我們不用顯式聲明。而且,因爲在方法的主體中只有一個表達式,我們也省略了括號,把整個方法的定義放到一行中。方法主體前面的等於號告訴編譯器函數的主體從這裏開始,就像我們看到的一樣。
    我們也在字面函數裏利用一些簡寫。之前我們像這樣寫一個函數:
    (s:String) => s.toUpperCase() 我們可以簡化成如下表達式:
    _.toUpperCase() 因爲map 接受一個參數,即一個函數,我們可以用 _ 佔位符來替代有名參數。也就是說,_ 像是一個匿名變量,在調用 toUpperCase 之前每一個字符串都會被賦值給它。注意,String 類型是被推斷出來的。將來我們會看到,Scala 還會在某些上下文中充當通配符。
    你可以在一些更復雜的字面函數中使用這種簡化的語法,就像我們將在《第3章 - 要點詳解》中看到的那樣。
    在最後一行,我們使用了一個object 而不是一個class 來簡化代碼。我們只要在Upper object 上直接調用upper 方法,而不用new Upper 來創建一個新的實例。(注意,這樣的語法看起來很像在Java 類中調用一個靜態方法。
    最後,Scala 自動導入了許多用以輸入輸出的方法,比如println,所以我們不用寫成Console.println()。我們只使用println 本身就可以了。(參見《第7章 - Scala Obejct 系統》的“預定義Object ”章節來獲取更多有關自動導入類型和方法的信息。)
    讓我們來做最後一次重構;讓我們把這段腳本變成一個編譯好的命令行工具。
    // code-examples/IntroducingScala/upper3.scala  object Upper {    def main(args: Array[String]) = {      args.map(_.toUpperCase())。foreach(printf("%s ",_))      println("")    }  } 現在upper 方法被重命名爲main。因爲Upper 是一個object,這個main 方法就像Java 類裏的static main 方法一樣。這個Upper 程序的入口。
    注意
    在Scala,main 必須是一個object 的函數。(在Java,main 必須是一個類的靜態方法。)命令行參數會作爲一個字符串數組被傳入應用程序,比如 args: Array[String]。
    main 方法的第一行使用了和我們剛纔產看過的map 方法一樣的簡寫。
    args.map(_.toUpperCase())… 調用map 會返回一個新的集合。我們用foreach 來遍歷它。我們在傳給foreach 的這個字面函數中再一次使用了一個 _ 佔位符。這樣,集合的每一個字符串會被作爲printf 的參數傳入。
    …foreach(printf("%s ",_)) 更清楚地說明一下,這兩個“_”是完全相互獨立的。這個例子裏的連鎖方法(Method Chaining)和簡寫字面函數需要花一些時間來習慣,但是一旦你熟悉了它們,他們用最少的臨時變量來產生可讀性很高的代碼。
    main 的最後一行在輸出中加入了一個換行。
    在這次,你必須先用scalac 來把代碼編譯成JVM 可認的。class 文件。
    scalac upper3.scala
    你現在應該有一個名爲Upper.class 的文件,就像你剛編譯了一個Java 類一樣。
    注意
    你可能已經注意到編譯器並沒有因爲文件名爲upper3.scala 而object 名爲Upper 而抱怨。不像Java,這裏文件名不用和公開域內的類型名字一致。(我們會在《第5章 - Scala 基礎面向對象編程》的“可見性規則”章節來探索可見性規則。)實際上,和Java 不同,你可以在一個單獨文件中有很多公開類型。此外,文件的地址也不用和包的聲明一致。不過,如果你願意,你可以依舊遵循Java 的規則。
    現在,你可以傳入任意多個字符串來執行這個命令了。比如:
    scala -cp . Upper Hello World! -cp 選項會把當前目錄加入到“類路徑”的搜索中去。你會得到如下輸出:
    HELLO WORLD! 這樣,我們已經滿足了一本編程語言書籍必須以一個“hello world ”程序開始的基本要求。
    初嘗併發
    被Scala 吸引有很多原因。 其中一個就是Scala 庫的Actors API。它基於Erlang [Haller2007] 強大的Actors 併發模型建立。這裏有一個例子來滿足你的好奇心。
    在Actor 併發模型[Agha1987] 中, 被稱爲執行者(Actor) 的獨立軟件實體不會互相之間共享狀態信息。 相反, 它們通過交換消息來通信。 沒有了共享易變狀態的需要, 就更容易寫出健壯的併發應用程序。
    在這個例子裏, 不同的圖形的實例被髮送到執行者(Actor )來進行繪畫和顯示。 想象這樣一個場景: 一個渲染集羣在爲動畫生成場景。 在場景渲染完成之後, 場景中的元圖形會被髮送到一個執行者中由顯示子系統處理。
    我們從定義一系列的Shape (形狀) 類開始。
    // code-examples/IntroducingScala/shapes.scala  package shapes {    class Point(val x: Double, val y: Double) {      override def toString() = "Point(" + x + "," + y + ")"    }    abstract class Shape() {      def draw(): Unit    }   class Circle(val center: Point, val radius: Double)     extends Shape {      def draw() = println("Circle.draw: " + this)      override def toString() =          "Circle(" + center + "," + radius + ")"    }    class Rectangle(val lowerLeft: Point, val height: Double, val width: Double)          extends Shape {      def draw() = println("Rectangle.draw: " + this)      override def toString() =        "Rectangle(" + lowerLeft + "," + height + "," + width + ")"    }    class Triangle(val point1: Point, val point2: Point, val point3: Point)          extends Shape {      def draw() = println("Triangle.draw: " + this)      override def toString() =        "Triangle(" + point1 + "," + point2 + "," + point3 + ")"    }  } 類Shape 的繼承結構在shapes 包(package)中定義。你可以用Java 的語法定義包,但是Scala 也支持類似於C# 的名稱空間的語法,就是把整個聲明都包含在大括號的域中,就像這裏所做的。Java 風格的包聲明語法並不經常用到,然而,它們都一樣精簡和可讀。
    類Point(點)表示了在一個平面上的二位點。注意類名字後面的參數列表。它們是構造函數的參數。在Scala 中,整個類的主體就是構造函數,所以你可以在類名字後面,類實體之前的主構造函數裏列出所有參數。(在《第5章 - Scala 的基本面向對象編程》的“Scala 的構造函數”章節中,我們會看到如何定義輔助的構造函數。)因爲我們在每一個參數聲明前放置了val 關鍵字,它們會被自動地轉換爲有同樣名字的只讀的字段,並且伴有同樣名字的公開讀取方法。也就是說,當你初始化一個Point 的實例時,比如point, 你可以通過point.x 和point.y 來讀取字段。如果你希望有可變的字段,那麼使用var 關鍵字。我們會在《第2章 - 打更少的字,做更多的事》的“變量聲明”章節來探索如何使用val 和var 關鍵字聲明變量。
    Point 類的主體定義了一個方法,類似於Java 的toString 方法的重寫(或者C# 的ToString 方法)。主意,Scala 像C# 一樣,在重寫一個具體方法時需要顯式的override 關鍵字。不過和C# 不一樣的是,你不需要一個virtual (虛擬)關鍵字在原來的具體方法上。實際上,在Scala 中沒有virtual 關鍵字。像之前一樣,我們省略了toString 方法主體兩邊的大括號“{…}”,因爲我們只有一個表達式。
    Shape 是一個抽象類。Scala 中的抽象類和Java 以及C# 中的很像。我們不能實例化一個抽象類,即使它們的字段和方法都是具體的。
    在這個例子裏,Shape 聲明瞭一個抽象的draw (繪製)方法。我們說它抽象是因爲它沒有方法主體。在方法上不用寫abstract (抽象)關鍵字。Scala 中的抽象方法就像Java 和C# 中的一樣。(參見《第6章 - Scala 高級面向對象編程》的“重寫Classes 和Traits 的成員”章節獲取更多細節。)
    draw 方法返回Unit,這種類型和Java 這樣的C 後繼語言中的void 大體一致。(參見《第7章 - Scala Object 系統》的“Scala 類型組織”來獲取更多細節。)
    Circle (圓)被聲明爲Shape 的一個具體的子類。 它定義了draw 方法來簡單地打印一條消息到控制檯。Circle 也重寫了toString。
    Rectangle 也是Shape 得一個具體子類,定義了draw 方法,重寫了toString。爲了簡單起見,我們假設它不會相對X 或Y 軸旋轉。於是,我們所需要的就是一個點,左下角的點就可以,以及長方形的高度和寬度。
    Triangle (三角形)遵循了同樣的模式。它獲取3個點作爲它的構造函數參數。
    在Circle,Rectangle 和Triangle 的所有draw 方法裏都用到了this。和Java,C# 一樣,this 是一個實例引用自己的方式。在這裏的上下文中,this 在一個String 的鏈接表達式(使用加號)的右邊,this.toString 被隱式地調用了。
    注意
    當然,在一個真正的程序中,你不會像這樣實現一個域模型裏的drawing 方法,因爲實現會依賴於操作系統平臺,繪圖API 等細節。我們會在《第4章 - Traits》裏看到一個更好地設計方式。
    既然我們已經定義了我們的形狀類型,讓我們回過頭來看Actors。我們定義了一個Actor 來接受消息(需要繪製的Shape)。
    // code-examples/IntroducingScala/shapes-actor.scala  package shapes {    import scala.actors._    import scala.actors.Actor._    object ShapeDrawingActor extends Actor {      def act() {        loop {          receive {            case s: Shape => s.draw()            case "exit"   => println("exiting…"); exit            case x: Any   => println("Error: Unknown message! " + x)          }        }      }    }  } Actor 被聲明爲shapes 包的一部分。接着,我們有兩個import (導入)表達式。
    第一個import 表達式導入了所有在scala.actors 包裏的類型。在Scala 中,下劃線_ 的用法和Java 中的星號* 的用法一致。
    注意
    因爲* 是方法名允許的合法字符,它不能在import 被用作通配符。所以,_ 被保留來作爲替代。
    Actor 的所有方法和公開域內的字段會被導入。Actor 類型中沒有靜態導入類型,雖然Java 中會。不過,它們會被導入爲一個object,名字一樣爲Actor。類和object 可以使用同樣的名字,就像我們會在《第6章 - Scala 高級面向對象編程》的“伴隨實體”章節中看到的那樣。
    我們的Actor 類定義,ShapeDrawingActor,是繼承自Actor (類型,不是實體)的一個實體。它的act 方法被重寫來執行Actor 的實際工作。因爲act 是一個抽象方法,我們不需要顯式地用override 關鍵字來重寫。我們的Actor 會無限循環來等待進來的消息。
    在每一次循環中,receive 方法會被調用。它會阻塞當前線程直到一個新的消息到來。爲什麼在receive 後面的代碼被包含在大括號{}中而不是小括號()呢?我們會在後面學到,有些情況下這樣的替代是被允許的,而且十分有用(參見《第3章 - Scala 本質》)。現在,我們需要知道的是,在括號中的表達式組成了一個字面函數,並且傳遞給了receive。這個字面函數給消息做了一個模式匹配來決定它被如何處理。由於case 語句的存在,它看上去像Java 中的一個典型的switch 表達式,實際上它們的行爲也很相像。
    第一個case 給消息做了一個類型比較。(在代碼中沒有爲消息實體做顯式變量聲明;它是被推斷出來的。)如果消息是Shape 類型的,第一個case 會被滿足。消息實體會被轉換成Shape 並且賦值給變量s,然後s 的draw 方法會被調用。
    如果消息不是一個Shape,第二個case 會被嘗試。如果消息是字符串 exit ,Actor 會打印一條消息然後結束執行。Actors 通常需要一個優雅退出的方式。
    最後一個case 處理所有其它任何類型的消息實例,作用和default (默認)case 一樣。Actor 會報告一個錯誤然後丟棄這個消息。Any 是Scala 類型結構中所有類型的父類型,就像Java 和其他類型語言中的Object 根類型一樣。所以,這個case 塊會匹配任何類型的消息。模式匹配是頭飢餓的怪獸,我們必須把這個case 塊放在最後,這樣它纔不會把我們需要的消息也都吃掉!
    回想一樣我們在Shape 類裏定義draw 爲一個抽象方法,然後我們在具體的子類裏實現它。所以,在第一個case 塊中的代碼執行了一個多態操作。
    模式匹配 vs. 多態
    模式匹配在函數式編程中扮演了中心角色, 就好像多態在面向對象編程中扮演着中心角色一樣。函數式的模式匹配比絕大多數像Java 這樣的命令式語言中的switch/case 語句更加重要和成熟。我們會在《第8章 - Scala 函數式編程》瞭解更多Scala 對於模式匹配支持的細節。在我們的這個例子裏,我們可以開始看到,函數式模式匹配和麪向對象多態調度的有力結合會給Scala 這樣的混合範式語言帶來巨大好處。
    最後,這裏有一段腳本來使用ShapeDrawingActor。
    // code-examples/IntroducingScala/shapes-actor-script.scala  import shapes._  ShapeDrawingActor.start()  ShapeDrawingActor ! new Circle(new Point(0.0,0.0), 1.0)  ShapeDrawingActor ! new Rectangle(new Point(0.0,0.0), 2, 5)  ShapeDrawingActor ! new Triangle(new Point(0.0,0.0),                                   new Point(1.0,0.0),                                   new Point(0.0,1.0))  ShapeDrawingActor ! 3.14159  ShapeDrawingActor ! "exit" 在shapes 包裏的所有形狀類會被導入。
    ShapeDrawingActor 會被啓動。默認情況下,它會運行在它自己的線程中(也有另外的選擇,我們會在《第9章 - 使用Actor 的健壯的,可伸縮的併發編程》中討論),等待消息。
    有5個消息通過使用語法 actor ! message 被送到Actor。第一個消息發送了一個Circle 實例。Actor 會“畫”出這個圓。第二個消息發送了Rectangle 消息。Actor 會“畫”出這個長方形。第三個消息對一個三角形做了同樣的事情。第四個消息發送了一個約等於Pi 的Double (雙精度浮點數)值。這對於Actor 來說是一個未知消息,所以它只是打印了一個錯誤消息。最後一個消息發送了exit 字符串,它會導致Actor 退出。
    要實驗這個Actor 例子,從編譯這兩個源文件開始。你可以從O'Reilly 下載網站獲取源代碼(參見前言中獲取代碼示例的部分來取得更多細節信息),或者你也可以自己創建它們。
    使用下面的命令來編譯文件。
    scalac shapes.scala shapes-actor.scala 雖然源文件的名字和位置並不和文件內容匹配,你會發現生成的class 文件被寫入到一個shape 文件夾內,每一個類都會有一個class 文件對應。這些class 文件的名字和位置必須和JVM 的需求相吻合。
    現在你可以運行這個腳本來看看Actor 的實際運行。
    scala -cp . shapes-actor-script.scala 你應該可以看到如下輸出。
    Circle.draw: Circle(Point(0.0,0.0),1.0)  Rectangle.draw: Rectangle(Point(0.0,0.0),2.0,5.0)  Triangle.draw: Triangle(Point(0.0,0.0),Point(1.0,0.0),Point(0.0,1.0))  Error: Unknown message! 3.14159  exiting… 要知道更多關於Actor 的細節,參加《第9章 - 使用Actor 的強壯的,可伸縮的併發編程》。
    概括
    我們通過Scala 的示例來讓你開始對Scala 有所瞭解,其中一個還給出了Scala Actors 庫的強大併發編程體驗。下面,我們會更深入Scala 語法,強調各種各樣快速完成大量任務的“鍵盤金融”方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章