- Scala編程(第5版)
- (德)馬丁·奧德斯基等
- 2464字
- 2022-05-06 15:51:27
1.2 是什么讓Scala能屈能伸
語言的伸縮性取決于很多因素,從語法細節到組件抽象都有。如果我們只能挑一個讓Scala能屈能伸的方面,那就是它對面向對象和函數式編程的結合(我們作弊了,面向對象和函數式本質上是兩個方面,不過它們確實是相互交織的)。
與其他結合面向對象和函數式編程的語言相比,Scala走得更遠。舉例來說,其他語言可能會區分對象和函數,將它們定義為不同的兩個概念,但在Scala中,函數值就是對象,而函數的類型是可被子類繼承的類。你可能會認為這僅僅是在紙面上更好看,但其實這對語言的伸縮性有著深遠的影響。本節將概要地介紹Scala是如何做到將面向對象和函數式概念結合在一起的。
Scala是面向對象的
面向對象編程獲得的成功是巨大的,從20世紀60年代中期的Simula和70年代的Smalltalk開始,直到現在,成了大多數編程語言都支持的主要特性。在某些領域,對象幾乎全面占領了市場。雖然面向對象的含義并沒有一個準確的定義,但是很顯然,對象這個概念是深受程序員群體歡迎的。
從原理上講,面向對象編程的動機非常簡單:除最微不足道的程序之外,所有程序都需要某種結構,而形成這種結構最直截了當的方式就是將數據和操作放進某種容器里。面向對象編程的偉大概念讓這類容器變得完全通用,使得這類容器既可以包含操作,也可以包含數據,并且可以以值的形式被存放在其他容器中,或者作為參數被傳遞給相關操作。這些容器被稱為對象。Smalltalk的發明人——Alan Kay認為,通過這樣的抽象,最簡單的對象也像完整的計算機一樣,有著相同的構造原理:它將數據和操作結合在一個形式化的接口之下。[7]所以說,對象與編程語言的伸縮性之間的關系很大:同樣的技巧既適用于小程序也適用于大程序。
雖然面向對象編程已經作為主流存在了很長的時間,但是相對而言很少有編程語言按照Smalltalk的理念,將這個構思原理推到邏輯的終點。舉例來說,許多語言都允許不是對象的值的存在,如Java的基本類型,或者允許不以任何對象的成員形式存在的靜態字段和方法的存在。這些對面向對象編程理念的背離在一開始看上去沒什么不妥,但它們傾向于讓事情變得復雜,限制了伸縮的可能性。
Scala則不同,它對面向對象的實現是純粹的:每個值都是對象,每個操作都是方法調用。舉例來說,當你說1 + 2時,實際上是在調用Int類里定義的名稱為+的方法。你也可以定義名稱像操作符的方法,這樣別人就可以用操作符表示法來使用你的API。
與其他語言相比,在組裝對象方面,Scala更為高級。Scala的特質(trait)就是一個典型的例子。特質與Java的接口很像,不過特質可以有方法實現甚至是字段。[8]對象通過混入組合(mixin composition)構建,構建的過程是取出某個類的所有成員,然后加上若干特質的成員。這樣一來,類的不同維度的功能特性就可以被封裝在不同的特質定義中。這粗看上去有點像多重繼承(multiple inheritance),細看則并不相同。與類不同,特質能夠對某個未知的超類添加新的功能,這使得特質比類更為“可插拔”(pluggable)。尤其是特質成功地避開了多重繼承中,當某個子類通過不同的路徑繼承到同一個超類時產生的“鉆石繼承”(diamond inheritance)問題。
Scala是函數式的
Scala不只是一門純粹的面向對象語言,也是功能完整的函數式編程語言。函數式編程的理念,甚至比計算器還要早。這些理念早在20世紀30年代由Alonzo Church開發的lambda演算中得以建立。而第一個函數式編程語言Lisp的歷史,可以追溯到20世紀50年代末期。其他函數式編程語言還包括:Scheme、SML、Erlang、Haskell、OCaml、F#等。在很長一段時間里,函數式編程都不是主流:雖然在學術界很受歡迎,但是在工業界并沒有廣泛使用。不過,最近幾年,大家對函數式編程語言和技巧的興趣與日俱增。
函數式編程以兩大核心理念為指導。第一個核心理念是函數是一等的值。在函數式編程語言中,函數值的地位與整數、字符串等是相同的。函數可以作為參數傳遞給其他函數,作為返回值返回,或者被保存在變量里。還可以在函數中定義另一個函數,就像在函數中定義整數那樣。也可以在定義函數時不指定名稱,就像整數字面量42,讓函數字面量散落在代碼中。
作為一等值的函數對操作的抽象和創建新的控制結構提供了便利。這種函數概念的抽象帶來了強大的表現力,可以讓我們寫出精簡可靠的代碼。這一點對于伸縮性也有很大的幫助。以ScalaTest為例,這個測試類庫提供了eventually(最后)這樣的結構體,接收一個函數作為入參(argument)。用法如下:

在eventually中的代碼——it.next() shouldBe 3這句斷言被包含在一個函數里,該函數并不會直接執行,而是會原樣傳入eventually。在配置好的時間內,eventually將會反復執行這個函數,直到斷言成功。
函數式編程的第二個核心理念是程序中的操作應該將輸入值映射成輸出值,而不是當場(in place)修改數據。為了理解其中的差別,我們不妨設想一下Ruby和Java的字符串實現。在Ruby中,字符串是一個字符型的數組,字符串中的字符可以被單個替換。例如,在同一個字符串對象中,可以將分號替換為句號。而在Java和Scala中,字符串是數學意義上的字符序列。通過s.replace(';', '.')這樣的表達式替換字符串中的某個字符,會交出(yield)一個全新的對象,而不是s。換句話說,Java的字符串是不可變的(immutable),而Ruby的字符串是可變的(mutable)。因此僅從字符串的實現來看,Java是函數式的,而Ruby不是。不可變數據結構是函數式編程的基石之一。Scala類庫在Java API的基礎上定義了更多的不可變數據類型。比如,Scala提供了不可變的列表(list)、元組(tuple)、映射(map)和集(set)等。
函數式編程的這個核心理念的另一種表述是方法不應該有副作用(side effect)。方法只能通過接收入參和返回結果這兩種方式與外部環境通信。舉例來說,Java的String類的replace方法便符合這個描述:它接收一個字符串(對象本身)、兩個字符,交出一個新的字符串,其中所有出現的入參第一個字符都被替換成了入參的第二個字符。調用replace并沒有其他的作用。像replace這樣的方法被認為是指稱透明(referential transparent)的,意思是對于任何給定的輸入,該方法調用都可以被其結果替換,同時不會影響程序的語義。
函數式編程鼓勵不可變數據結構和指稱透明的方法。某些函數式編程語言甚至強制要求這些。Scala給你選擇的機會。如果你愿意,則完全可以編寫指令式(imperative)風格的代碼,也就是用可變數據和副作用編程。不過Scala通常讓你可以不必使用指令式的語法結構,因為有其他更好的函數式的替代方案可供選擇。
- Functional Python Programming
- DBA攻堅指南:左手Oracle,右手MySQL
- Android開發精要
- Java從入門到精通(第4版)
- 概率成形編碼調制技術理論及應用
- Python機器學習經典實例
- jQuery開發基礎教程
- VMware虛擬化技術
- NGINX Cookbook
- 大話Java:程序設計從入門到精通
- Node.js 12實戰
- uni-app跨平臺開發與應用從入門到實踐
- Scala Functional Programming Patterns
- Oracle Data Guard 11gR2 Administration Beginner's Guide
- Learning Grunt