官术网_书友最值得收藏!

2.1 Scala語言概述

本節首先對計算模型的理論研究歷史和編程范式的發展進行了簡要的概述,然后介紹了Scala語言的發展背景及基本特性,最后詳細介紹了Scala的安裝方法和各種運行方式。

2.1.1 計算機的緣起

從20世紀30年代開始,一些數學家開始研究如何設計一臺擁有無窮計算能力的超級機器,來幫人類自動完成一些計算問題。

數學家阿隆佐·邱奇(Alonzo Church)提出了“λ演算”的概念,這是一套用于研究函數定義、函數應用和遞歸的形式系統。λ演算被視為最小的通用程序設計語言,它包括一條變換規則(變量替換)和一個函數定義方式。λ演算的通用性就體現在,任何一個可計算函數都能用這種形式來表達和求值。λ演算強調的是變換規則的運用,而非實現它們的具體機器??梢哉J為這是一種更接近軟件而非硬件的方式。它是一個數理邏輯形式系統,使用變量代入和置換來研究基于函數定義和應用的計算。

英國數學家阿蘭·圖靈(Alan Turing)采用了完全不同的設計思路,提出了一種全新的抽象計算模型,即將人們使用紙筆進行數學運算的過程進行抽象,由一個虛擬的機器替代人們進行數學運算。所謂的圖靈機就是指一個抽象的機器,如圖2-1所示,它有一條無限長的紙帶,紙帶分成了一個一個的小方格,每個方格有不同的顏色。有一個機器頭在紙帶上移來移去。機器頭有一組內部狀態,還有一些固定的程序。在每個時刻,機器頭都要從當前紙帶上讀入一個方格信息,然后結合自己的內部狀態查找程序表,根據程序輸出信息到紙帶方格上,并轉換自己的內部狀態,然后進行移動。這種理論計算模型后來被稱為“圖靈機”,它是現代計算機的鼻祖?,F有理論已經證明,λ 演算和圖靈機的計算能力是等價的。

圖2-1 圖靈機示意模型

如果說圖靈奠定的是計算機的理論基礎,那么馮·諾依曼(John Von Neumann)則是將圖靈的理論物化成為實際的物理實體,成為了計算機體系結構的奠基者。1945年6月,馮·諾依曼提出了在數字計算機內部的存儲器中存放程序的概念,這是所有現代計算機的范式,被稱為“馮·諾依曼結構”,按這一結構建造的計算機被稱為“存儲程序計算機”,又稱為“通用計算機”。馮·諾依曼計算機主要由運算器、控制器、存儲器和輸入輸出設備組成,它的特點是,程序以二進制代碼的形式存放在存儲器中,所有的指令都是由操作碼和地址碼組成,指令在其存儲過程中按照執行的順序,以運算器和控制器作為計算機結構的中心等。從第一臺馮·諾依曼計算機誕生到今天已經過去很多年了,計算機的技術與性能也都發生了巨大的變化,但是,計算機主流體系結構依然是馮·諾依曼結構。

2.1.2 編程范式

編程范式是指計算機編程的基本風格或典范模式。常見的編程范式主要包括命令式編程和函數式編程。面向對象編程就屬于命令式編程,比如C++、Java等。

命令式語言是植根于馮·諾依曼體系的,一個命令式程序就是一個馮·諾依曼機的指令序列,給機器提供一條又一條的命令序列讓其原封不動地執行。函數式編程,又稱泛函編程,它將計算機的計算視為數學上的函數計算,并且避免狀態以及可變數據。函數編程語言最重要的基礎是λ演算,而且λ演算的函數可以接受函數當作輸入和輸出,因此,λ演算對函數式編程特別是Lisp語言有著巨大的影響。典型的函數式語言包括Haskell、Erlang和Lisp等。

從理論上說,函數式語言并不是通過馮·諾依曼機運行的,而是通過 λ 演算來運行的,但是,由于現代計算機都是采用馮·諾依曼結構,所以,函數式程序還是會被編譯成馮·諾依曼機的指令來執行。

一個很自然的問題是,既然已經有了命令式編程,為什么還需要函數式編程呢?為什么在C++、Java 等命令式編程流行了很多年以后,近些年函數式編程會迅速升溫呢?這個問題的答案需要從CPU制造技術的變化說起。從20世紀80年代至今,CPU的制造工藝不斷提升,晶體管數量不斷增加,運行頻率不斷提高,如圖2-2所示。在30多年里,CPU的處理速度已經從10MHz提高到3.6GHz。但是,CPU的制造工藝不可能無限提升,單個CPU內集成的晶體管數量不可能無限增加,因此,從2005年以來,計算機計算能力的增長已經不依賴CPU主頻的增長,而是依賴于CPU核數的增多, CPU開始從單核發展到雙核,再到四核甚至更多核數。

對于命令式編程而言,由于涉及多線程之間的狀態共享,就需要引入鎖機制實現并發控制。而函數式編程則不會在多個線程之間共享狀態,不會造成資源爭用,也就不需要用鎖機制來保護可變狀態,自然也就不會出現死鎖,這樣可以更好地實現并行處理。因此,函數式編程能夠更好地利用多個處理器(核)提供的并行處理能力,所以,函數式編程開始受到更多的關注。此外,由于函數式語言是面向數學的抽象,更接近人的語言,而不是機器語言,因此,函數式語言的代碼會更加簡潔,也更容易被理解。

圖2-2 1970~2010年CPU的工藝參數及性能變化

2.1.3 Scala簡介

編程語言的流行主要歸功于其技術上的優勢以及其對某種時代需求的適應性。例如,Java 的流行主要歸功于其跨平臺特性和互聯網應用的廣泛需求。從2010年前后開始,隨著物聯網及大數據等技術的發展,編程語言面對的是高并發性、異構性以及快速開發等應用場景,這些場景使得函數式編程可以大施拳腳。但傳統的面向對象編程的統治地位遠沒有結束,因此,能夠將兩者結合起來的混合式編程范式是適應當前需求的最好解決方案。Scala語言正是在這一背景下開始流行起來的。

Scala是由瑞士洛桑聯邦理工學院(EPFL)的Martin Odersky教授,于2001年基于Funnel的工作開始設計的。Scala是一門類Java的多范式語言,它整合了面向對象編程和函數式編程的最佳特性。具體而言:

Scala運行于Java虛擬機(JVM)之上,并且兼容現有的Java程序,可以與Java類進行互操作,包括調用Java方法、創建Java對象、繼承Java類和實現Java接口。

Scala是一門純粹的面向對象的語言。在Scala語言中,每個值都是對象,每個操作都是方法調用。對象的數據類型以及行為由類和特質描述。類抽象機制的擴展有兩種途徑,一種途徑是子類繼承,另一種途徑是靈活的混入(Mixin)機制,這兩種途徑能避免多重繼承的諸多問題。

Scala也是一門函數式語言。在Scala語言中,每個函數都是一個對象,并且和其他類型(如整數、字符串等)的值處于同一地位。Scala提供了輕量級的語法用以定義匿名函數,同時支持高階函數,允許嵌套多層函數,并支持柯里化(Currying)。

Scala 語言的名稱來自于 Scalable,意為“可伸展的語言”。Scala 的“可伸展性”歸功于其集成了面向對象和函數式語言的優點。Scala的一種常用方式是通過解釋器鍵入單行表達式來即時運行并觀察結果,因此,對于某些應用來說,Scala就像是一種腳本語言,它的語法簡單,在變量類型自動推斷機制下,無需時刻關注變量的類型,但卻保留了強制靜態類型的諸多優勢。同時,Scala編寫的程序也可以編譯打包發布,其生成的代碼與Java是一樣的。已經有許多大型的業務系統或框架采用Scala作為首選的開發語言,包括Spark、Twitter和LinkedIn等。

2.1.4 Scala的安裝

Scala于2004年1月公開發布1.0版本,目前仍處于快速發展階段,每隔幾個月就有新的版本發布。Spark從2.0版本開始都采用Scala2.11編譯,因為本教材使用的Spark版本是2.1.0,其對應的Scala版本是2.11.8,所以,本教材中的Scala選用2017年4月發布的2.11.8版本。

Scala運行在Java虛擬機(JVM)之上,因此只要安裝相應的Java虛擬機,所有的操作系統都可以運行Scala程序,包括Window、Linux、Unix、Mac OS等。本教材后續的Spark操作都是在Linux系統下進行的,因此,這里以Linux系統(選用Ubuntu發行版)為例,簡要介紹Scala的安裝及環境配置。

首先,需要安裝Linux系統,具體安裝方法請參見教材官網的“實驗指南”欄目的“Linux系統的安裝”。此外,在安裝Scala之前,請確保本機Linux系統中已經安裝了Java 7或以上版本的JDK (Scala 2.12需要Java 8支持),具體安裝方法請參考教材官網的“實驗指南”欄目的“Linux系統中Java的安裝”。

然后,下載相應的二進制包完成 Scala 的安裝。以2.11.8版本為例,通過官方網站(http://www.scala-lang.org/download/2.11.8.html)下載Linux系統對應的安裝壓縮包“scala-2.11.8.tgz”,或者也可以直接從教材官網的“下載專區”的“軟件”目錄中下載“scala-2.11.8.tgz”;在Linux系統中下載和解壓縮文件的具體方法,可以參考本教材官網的“實驗指南”欄目的“Linux系統中下載安裝文件和解壓縮方法”。下載后,將“scala-2.11.8.tgz”解壓到Linux系統的本地文件夾下,如/usr/local,這時會在該目錄下生成一個新的文件夾“scala-2.11.8”,編譯器及各種庫文件即位于該文件夾下,為以后啟動Scala方便,可以把“scala-2.11.8”重命名為“scala”,并建議將其下的“bin”目錄添加到PATH環境變量。在終端(即Linux Shell環境)中運行“scala -version”,查看是否正確安裝,如果已經正確安裝,則會顯示Scala編譯器的版本信息。

2.1.5 HelloWorld

開始學習Scala的最簡單的方法是使用Scala REPL(REPL是Read、Eval、Print、Loop的縮寫)。與Python及MATLAB等語言的解釋器一樣,Scala REPL是一個運行Scala表達式和程序的交互式Shell,在REPL里輸入一個表達式,它將計算這個表達式并打印結果值。在Linux系統中打開一個終端(可以使用組合鍵Ctrl+Alt+T),輸入如下命令啟動Scala REPL:

$ cd /usr/local/scala #這是Scala的安裝目錄

$ ./bin/scala

正常啟動后,終端將出現“scala>”提示符,此時即可輸入Scala表達式,默認情況下一行代表一個表達式或語句,按Enter鍵后scala即運行該語句或表達式并提示結果,如果一條語句需要占用多行,只需要在一行以一個不能合法作為語句結尾的字符結束(比如句點或未封閉的括號與引號中間的字符),則REPL會自動在下一行以“|”開頭,提示用戶繼續輸入。圖2-3所示是幾個輸入的示例,其中,關于函數show()的定義語句占用了多行。

圖2-3 在Scala REPL中一條語句占用多行的效果

直接在解釋器里編寫程序,一次只能運行一行。如果想運行多行,可以用腳本的方式運行,只需要將多行程序保存為文本文件,然后在Linux Shell中用“scala 文件名”這種命令形式來運行即可,或者在Scala解釋器終端用“:load 文件名”的形式裝載執行。下面請在Linux系統中打開一個終端,在“/usr/local/scala/”目錄下創建一個mycode子目錄,在mycode目錄下,使用文本編輯器(比如vim)創建一個代碼文件Test.scala(文本編輯器vim的使用方法可以參見教材官網的“實驗指南”欄目的“Linux系統中vim編輯器的安裝和使用方法”)。Test.scala的內容如下:

//代碼文件為/usr/local/scala/mycode/Test.scala

println("This is the first line")

println("This is the second line")

println("This is the third line")

然后,可以在Scala REPL中執行如下命令運行該代碼文件:

scala> :load /usr/local/scala/mycode/Test.scala

Loading /usr/local/scala/mycode/Test.scala…

This is the first line

This is the second line

This is the third line

除了使用Scala解釋器運行Scala程序之外,還可以通過編譯打包的方式運行Scala程序。

首先,在Linux系統中創建一個代碼文件HelloWorld.scala,內容如下:

//代碼文件為/usr/local/scala/mycode/HelloWorld.scala

object HelloWorld {

 def main(args: Array[String]) {

 println("Hello, world!");}

}

使用如下命令對代碼文件HelloWorld.scala進行編譯:

$ cd /usr/local/scala/mycode

$ scalac HelloWorld.scala

編譯成功后,將會生成“HelloWorld$.class”及“HelloWorld.class”兩個文件,其文件名即為程序中用object關鍵字定義的對象名,后一個文件即是可以在Java虛擬機上運行的字節碼文件。然后可以使用如下命令運行該程序:

$ scala -classpath .HelloWorld

注意,上面命令中一定要加入“-classpath .”,否則可能會出現“No such file or class on classpath:HelloWorld”錯誤??梢钥闯觯@與編譯和運行Java的“Hello World”程序非常類似,事實上,Scala的編譯和執行模型與Java是等效的,上面編譯后的字節碼文件也可以直接用Java執行,命令如下:

$ java -classpath .:/usr/local/scala/lib/scala-library.jar HelloWorld

其中,scala-library.jar為Scala類庫。

關于上面的Scala源文件HelloWorld.scala,這里對照Java做幾點簡要說明。

(1)HelloWorld是用關鍵字object定義的單例對象(Singleton Object),它提供了一個main方法用作應用程序的入口點。這里要注意Scala的main方法與Java的main方法之間的區別,Java中是用靜態方法(public static void main(String[] args)),而Scala沒有提供靜態方法,改為使用單例對象方法。每一個獨立應用程序都必須有一個定義在單例對象中的main方法。

(2)盡管對象的名字HelloWorld與源文件名稱HelloWorld.scala一致,但是對于Scala而言,這并不是必須的。實際上,可以任意命名源文件,比如這里可以把源文件命名為 Test.scala,源文件里面的單例對象名稱使用HelloWorld。可以看出,在這個方面,Scala和Java是不同的,按照Java的命名要求,這里的文件名稱就只能起名為 HelloWorld.scala,也就是文件名稱必須和文件中定義的類(class)名稱保持一致。雖然 Scala 沒有要求文件名和單例對象名稱一致,但是,這里仍然推薦像在Java里那樣按照所包含的類名來命名文件,這樣程序員可以通過查看文件名的方式方便地找到類。

(3)Scala 是大小寫敏感的語言,采用分號“;”分隔語句,但與 Java 不同的是,一行 Scala程序的最后一條語句末尾的分號是可以省略的。在 HelloWorld.scala 中,第三行末尾的分號是可以省略的。

主站蜘蛛池模板: 大丰市| 金山区| 莎车县| 榆中县| 汕头市| 宁强县| 周口市| 元朗区| 临漳县| 沛县| 台山市| 佛教| 军事| 永善县| 天祝| 太保市| 稻城县| 南乐县| 林甸县| 嘉祥县| 天水市| 花垣县| 浙江省| 海盐县| 浮梁县| 朝阳区| 无棣县| 桐梓县| 灵台县| 武山县| 平远县| 正蓝旗| 买车| 施甸县| 肇州县| 泸州市| 晋中市| 江源县| 虞城县| 华池县| 双流县|