譯者序
Scala3終于來了。
這是一次重大的更新。Scala編輯器從底層開始被全部重寫。不得不說,這也是Scala創始團隊的一次冒險,畢竟已經有很多成功的公司、團隊和個人在早期版本,尤其是Scala2.8之后的Scala生態中找到了自己的位置、大量以Scala編寫的類庫和業務代碼在線上運行。
可Martin Odersky 和他的團隊就是聊足了勁要升級,想從根本上修復那些讓Scala還不夠理論完備的設計。但是理論完備有那么重要嗎?站在編程語言設計者的角度。我傾向于肯定的答案。而站在使用者的角度。我更關心它帶來了那些變化,以及我能不能在使用中適癥這些變化
讓我頗感意外的是,真正上手Scala3,并沒有想象中的那么困難重重。甚至抱受爭議的縮進語法,也沒有帶來任何不適。原有的代碼不需要修改,或者僅僅需要少量修改。就能順利地通過Scala3的編譯。你很難相信這是一門全新的、從里到外重新實現的編程語言。
經過短暫的適應期。Scala3的諸多新特性,如枚舉、上下文參數、擴展方法和類型族等,都能被我靈活運用。這些新特性初看起來各有各的模樣,并且實際使用下來,也多少都帶著一些新編譯器的味道,但是你能嗅到類型系統的某種一致性。Scala一直能在很多看似完全不同的概念之間找到關聯、似乎總能透過表象,直面本質。這種感覺很微妙、讓人既有些新奇,又非常熟悉。
Scala 社區這幾年也有非常大的變化和進步,值得一提的是,ZIO 這個面向作用(effect)的編程類庫對業務代碼中常見的模式進行了非常有洞見性的抽象,將函數式編程以一種類型健壯且高效的方式引人一線開發者的工具庫中。很自然地,ZIO 在順利發布1.0版本之后,隨著Scala 3的發布,也在時間全面擁抱Scala 3。
你手中的這本《Scala 編程》已經是第5版,這一版針對Scala3進行了全面的更新。更新主要體現在兩個方面:首先,增加了對 Scala3主要的新特性的介紹,所有內容和示例代碼都基于Scala3重新梳理和編寫;其次,在篇幅上進行了大幅度調整,顯著的是把《Scala 編程》拆成了兩卷,卷也就是現在這本《Scala編程》,它保留了 Scala 編程語言核心功能特性和設計理念的內容,讓大家能夠快速地理解、掌握并使用Scala編寫實用的類庫和業務代碼,而第二卷為《Scala高級編程》,它將包含更多高級主題,且單獨成冊,面向那些對 Scala高級特性(如宏和元編程)感興趣的讀者。
感謝 Martin Odersky 和他的團隊為我們帶來這樣一門獨特而優雅的編程語言,并且不惜冒著社區分化的風險(現在看來這個擔心很可能是多余的)堅持對語言核心進行升級;感謝電子工業出版社及張春雨編輯,在第1版、第3版和第4版之后,繼續引進本書的第5版;感謝編輯團隊和其他幕后工作者的辛勤付出;感謝家人無條件的支持和鼓勵,你們是我永遠的摯愛。
在本書的翻譯過程中,譯者雖已盡力忠實呈現原著的本意,但畢竟能力有限,問題和疏漏恐難以避免,懇請各位讀者批評指正、聯系地址: gaoyuxiang.scala@gmail.com。
高宇翔 2021年于上海
序
見證一門新編程語言的誕生是一件有趣的事。對任何一位使用編程語言的人而言無論你是首次嘗試編程的人還是職業軟件工程師編程語言看起來就在那里。就像錘子或斧子一樣,編程語言也是一種工具,讓我們可以做我們想做的。我們很少會想到工具是怎么來的,它的設計過程是怎樣的。或許我們對工具的設計有自己的看法,但除此之外,我們通常只能接受并運用它。
編程語言的創造過程讓我有了完全不同的視角。各種可能性似乎無窮無盡。同時,編程語言也必須滿足看起來同樣無窮無盡的各種約束。這是一種奇怪的張力。
創造一門新的編程語言有很多方面的原因,例如,某種個人想要解決的痛點,或者某種學術上的洞見,或者技術債,或者其他編譯器架構的潛在收益,甚至可能是政治因素。對Scala 3的創造而言,上述原因多少都有一些。
無論出于何種原因,一切都開始于Martin Odersky在某一天突然消失,當他幾天后再次出現在某個研究組會議上時,他向大家正式宣告自己已經開始嘗試從零起步編寫一個全新的編譯器,將DOT演算 實施落地。而在場的我們是一群博士研究生和博士后,在那之前主要負責Scala 2的開發和維護。當時,Scala看起來正在接近看上去難以企及的成功高度,尤其是對于這樣一門誕生于瑞士的、一個聽起來有些奇怪的學校的偏門學術編程語言。然而就在不久前,Scala得到了舊金山灣區的很多創業公司的追捧,成立了Typesafe也就是后來的Lightbend,專注于支持、維護和管理Scala 2。那么為什么突然要做一個全新的編譯器,以及由此帶來的不一樣的編程語言呢?我們當中的大多數人對此心存疑慮,但Martin Odersky已經下定了決心。
幾個月過去了。就像上了發條一樣,每天中午12點,整個實驗室的人都會出現在連接各個辦公室的門廳。當聚集了一定數量的人員以后,我們就會一起來到EPFL的某個餐廳吃午飯,并享用飯后咖啡。在每天都會舉行的這個儀式中,關于新編譯器的想法是反復出現的討論話題,例如,從150%兼容Scala 2(避免陷入Python 2和Python 3的困境),到創造一門全新的全光譜依賴類型編程語言。
研究組中持懷疑態度的人,一個接一個地被Scala 3的某個特性征服,比如,對類型檢查器的精簡,全新的編譯器架構,以及對類型系統的增強等。隨著時間的推移,社區主流也認為Scala 3相比Scala 2而言具有顯著的改進。對于這個結論,不同的人有不同的理由。對有些人而言,是因為Scala 3將花括號和條件判定語句的括號變為可選的,從而改善了可讀性。對其他人而言,是因為Scala 3的類型系統更強了。如此種種。
我可以很有信心地說,Scala 3的設計并不是完全依靠直覺的閉門造車,而是吸納了過去設計的寶貴經驗,以及EPFL研究組和Scala社區的多年溝通與交流經驗。并且,除從頭開始在全新的地基上搭建之外,別無他途。既然Scala 3是從頭開始設計的,其內核就是一門全新的編程語言。
Scala 3是一門全新的編程語言。誠然,它兼容Scala 2,聽起來像是一門已經存在的編程語言的第三個重大版本。但是不要被這個影響了你的判斷,Scala 3實現了在Scala 2中先行試驗、探索的諸多想法的重大精簡。
在Scala 3的所有特性中,可能Scala的一個專屬的變化是對隱式的改動。Scala從一開始就被聰明的程序員們用來實現某種基于Scala特性本就很少有人能想到的功能,更別提這些功能與Scala設計本意的背離程度有多大了。這個先前被稱作隱式的特性可能是Scala中著名的被用來以各種奇怪的方式改變Scala 2代碼行為的功能點了。隱式的使用場景包括:對一個類在事后追加方法,不擴展也不重新編譯;或者,在某種特定的上下文中,基于某種類型簽名,自動選擇適用于該上下文的正確實現。上述只是冰山一角我們甚至為此寫了一篇論文以對開發人員使用隱式的各種方法進行歸類。
這就像是把旋鈕和杠桿交給用戶,期待他們能做出一臺精密的儀器,如機械計算器。但通常我們得到的是類似于Theo Jansen的動力雕塑,而不是某種能一眼看出用途的物件。簡單而言,如果你交給編程社區的是一些旋鈕和杠桿,則社區中那些強悍的選手總能找到這些工具的創新用法。這是人的本性。不過可能正是在這里,Scala 2犯了錯誤,將基礎、通用的旋鈕和杠桿交給了程序員。
我想說的是,在Scala 2中,隱式有無窮無盡的可能性,這些可能性足夠我們撰寫研究生論文,而社區對于如何使用隱式并沒有一個統一的認識。這種沒有清晰用途的編程語言特性不應該存在。但是很可惜,隱式就是這樣一種存在:很多人將隱式看作Scala獨有的強大功能,沒有其他語言能做到;還有很多人認為隱式是神秘且經常令人困惑的機制,會侵入你的代碼,將你的代碼改得面目全非。
你可能已經聽說過很多種不同形式的表述,但Scala 3代表了這之前所有Scala版本的簡化版本。隱式是一個很好的例子。在意識到那些后空翻程序員希望通過隱式來實現更廣泛的編程模式[如類型族(typeclass)派生]后,Martin Odersky在其他人的幫助下得出的結論是,我們不應該把注意力集中在人們一般如何使用隱式作為編程機制,而應該關注程序員們想用隱式做什么,然后把這個目標變得更加容易且高效。這就是口頭禪Scala 3專注于意圖而不是機制的來源。
Scala 3并不把注意力集中在作為編程機制的隱式的通用性上,而是關注開發人員在使用隱式時想要滿足的特定使用場景,讓其用起來更加直接。例如,隱式地將上下文或配置信息傳遞給某方法,而不需要程序員顯式地給出重復的參數;在事后給類追加方法;在算術運算中對不同類型的值進行轉換。如今,Scala 3將這些使用方法直接提供給程序員,使他們不需要深入理解Scala編譯器如何解析隱式值,只需要關心在不重新編譯Bar類的前提下給Bar類追加foo方法這樣的任務即可;不需要具有博士學位,只需要把之前的隱式替換成其他更直接的與特定使用場景相關的關鍵字即可,如given和using。更多內容參見第21章和第22章。
專注于意圖而不是機制的故事并不止于對隱式的改造,這個設計哲學幾乎貫穿了這門語言的方方面面。例如,對Scala類型系統的增強和簡化,包括并集類型(union type)、枚舉(enum)、匹配類型(match type)等;對Scala語法的清理,包括if、else、while,讓條件判斷讀起來更像英文。
當然,我說的這些,你不必盲目相信。無論你是Scala新手還是有經驗的Scala開發人員,我希望你和我一樣,Scala 3所包含的許多新的設計理念能讓你感到耳目一新且直截了當!
Heather Miller 瑞士洛桑
引言
本書是Scala編程語言的教程,由直接參與Scala開發的人來編寫。我們的目標是讓讀者通過本書,能夠了解和掌握成為高產的Scala程序員需要知道的一切。書中的所有示例均能通過Scala 3.0.0的編譯。
誰讀本書
本書主要的目標讀者是希望學習如何使用Scala編程的人。如果你想在你的下一個項目中使用Scala,本書就是為你準備的。除此之外,本書對于那些想要學習新知識從而開闊自己眼界的程序員也同樣有益。比方說,如果你是Java程序員,那么閱讀本書,你將接觸到來自函數式編程領域和高階面向對象領域的許多概念。我們相信,通過學習Scala及Scala背后的觀念,你將成為一名更好的程序員。
我們假定你擁有常規的編程知識。雖然Scala作為用于入門的編程語言并沒有什么不妥,但是本書并不適用于(從零開始)學習編程。
另一方面,閱讀本書并不要求讀者具備某項具體的編程語言的知識。我們當中大部分人都是在Java平臺上使用Scala的,但本書并不假定你了解Java本身。不過,我們預期大部分讀者都熟悉Java,因此我們有時會將Scala與Java做對比,幫助這些讀者理解它們之間的區別。
如何使用本書
本書的主旨是教學,我們推薦的閱讀順序是從前到后,依次閱讀各章。我們盡可能每次只引入一個主題,同時只使用已經介紹過的主題來解釋這個新的主題。因此,如果你跳過前面的章節,則可能會遇到某些并不十分理解的概念。只要你按順序閱讀,就會發現掌握Scala是循序漸進、順理成章的。
如果你看到某個不明白的詞匯,記得查看術語表。許多讀者都喜歡快速瀏覽特定的章節,這沒有問題,目錄能幫助你隨時找回閱讀的坐標和方位。
當你讀完本書以后,還可以繼續將其當作語言參考書。Scala編程語言有一份正式的語言規范,但語言規范強調的是精確性,而不是可讀性。雖然本書不會覆蓋Scala的每一個細節,但是它也足夠全面,應該能夠在你逐漸成為Scala編程能手的過程中,承擔起語言參考書的職責。
如何學習Scala
通讀本書,你可以學到很多關于Scala的知識。不過,如果你做一些額外的嘗試,則可以學得更快,更徹底。
首先,利用好包含在本書中的代碼示例。手動將這些代碼示例錄入,有助于在腦海中逐行過一遍代碼。尤其是在錄入過程中嘗試一些變化,會非常有趣,這也能讓你確信自己真的理解了它們背后的工作原理。
其次,時常訪問在線論壇。這樣,你和其他Scala愛好者可以互相促進。網上有大量的郵件列表、討論組、聊天室、Wiki和Scala特定主題的訂閱。花費一些時間,找到滿足你需求的內容,你會在小問題上花更少的時間,有更多的時間和精力投入更深入、更重要的問題中。
后,一旦你讀得足夠多,就可以自己啟動一個編程項目。例如,從頭編寫小程序,或者為某個更大的項目開發組件,因為僅僅閱讀并不會讓你走得更遠。
內容概覽
第1章,一門可伸縮的語言,主要介紹Scala的設計及背后的概念和歷史。
第2章,Scala入門,介紹了如何使用Scala完成一些基礎的編程任務,但并不深入講解它是如何工作的。本章的目標是讓你可以開始輸入Scala代碼并執行。
第3章,Scala入門(續),展示了更多基本的編程任務,幫助你快速上手Scala。學習完本章以后,你應該就能使用Scala完成簡單的腳本型任務了。
第4章,類和對象,開始深入介紹Scala,描述其基本的面向對象的組成部分,并指導大家如何編譯并運行Scala應用程序。
第5章,基本類型和操作,介紹了Scala基本類型、字面量和支持的操作,(操作符的)優先級和結合律,以及對應的富包裝類。
第6章,函數式對象,以函數式(即不可變)的分數為例,更深入地講解Scala面向對象的特性。
第7章,內建的控制結構,展示了如何使用Scala內建的控制結構:if、while、for、try和match。
第8章,函數和閉包,給出了對函數的深入介紹,而函數是函數式編程語言基本的組成部分。
第9章,控制抽象,展示了如何通過定義自己的控制抽象來對Scala基本的控制結構進行完善和補充。
第10章,組合和繼承,更進一步探討Scala對面向對象編程的支持。本章的主題不像第4章那么基礎,但在實踐中經常會遇到。
第11章,特質,介紹了Scala的混入組合機制。本章展示了特質的工作原理,描述了特質的常見用法,并解釋了特質相對于更傳統的多重繼承有哪些改進。
第12章,包、引入和導出,討論了大規模編程實踐中我們會遇到的問題,包括包,import語句,以及像protected和private那樣的訪問控制修飾符。
第 13 章,樣例類和模式匹配,介紹了這組孿生的結構。它們在處理樹形的遞歸數據時非常有用。
第14章,使用列表,詳細地解釋了列表這個在Scala程序中使用普遍的數據結構。
第15章,使用其他集合類,展示了如何使用基本的Scala集合,如列表、數組、元組、集和映射。
第16章,可變對象,解釋了可變對象,以及Scala用來表示可變對象的語法。本章以一個具體的離散事件模擬案例分析收尾,展示了實踐中可變對象的適用場景。
第17章,Scala的繼承關系,解釋了Scala的繼承關系,并探討了通用方法和底類型等概念。
第18章,類型參數化,使用具體的示例解釋了第13章介紹過的信息隱藏的技巧:為純函數式隊列設計的類。本章接下來對類型參數的型變進行了說明,介紹了類型參數化對于信息隱藏的作用。
第 19 章,枚舉,介紹了枚舉和代數數據類型(ADT)這組孿生的結構,讓你更好地編寫規則的、開放式的數據結構。
第20章,抽象成員,描述了Scala支持的各種抽象成員,不僅方法可以被聲明為抽象的,字段和類型也可以。
第21章,上下文參數,介紹了Scala如何幫助你對函數使用上下文參數。將所有的上下文信息都直接帶入并不是什么難事,但會因此增加很多樣板代碼,上下文參數能幫助你減少一些樣板代碼。
第22章,擴展方法,介紹了Scala如何讓一個在類定義之外的函數看起來像是類自己定義的那樣的機制。
第23章,類型族,展示了類型族的若干示例。
第24章,深入集合類,詳細介紹了Scala集合類庫。
第25章,斷言和測試,展示了Scala的斷言機制,并介紹了用Scala編寫測試的若干工具,特別是ScalaTest。