又一篇寫給小師妹的Scala學(xué)習(xí)筆記·一

每天兩頁,分享《Programming in Scala》的心得。

這個(gè)系列之前其實(shí)寫過。具體可以看:

寫給小師妹的Scala學(xué)習(xí)筆記·開篇
寫給小師妹的Scala學(xué)習(xí)筆記·二

當(dāng)時(shí)是為了快速“學(xué)會(huì)”Scala,選擇了疾風(fēng)式的搞法,幾周就擼完一本書。

這次我們慢下來,找一本Scala作者自己寫的書,仔細(xì)品一品Scala的設(shè)計(jì)哲學(xué),并且以日記的形式記錄一下所思所想。

以下是正文部分。

2022-01-02

今天讀的這一章的大標(biāo)題叫做“ A Scalable Language”。Scala這個(gè)名字,源于作者希望創(chuàng)造一種Scala-ble的語言。

接下來,他簡要的介紹了Scala的幾個(gè)特性:

首先,它是一種運(yùn)行在JVM上的語言,因此和Java具有“無縫”的交操作性。借助于Java強(qiáng)大的生態(tài),可以少造無數(shù)輪子。當(dāng)然像Groovy、Clojure等等都有這樣的特性。

其次,Scala兼有面向?qū)ο蠛秃瘮?shù)式2種編程范式(這一點(diǎn)其實(shí)是相當(dāng)不常見的),而且是一門靜態(tài)類型的語言(所謂動(dòng)態(tài)一時(shí)爽,重構(gòu)火葬場,靜態(tài)語言,可以讓編譯器替你干不少臟活累活)。函數(shù)式的一面使得它易于構(gòu)建小組件,面向?qū)ο笫沟盟梢杂脕順?gòu)建大型程序。

接下來作者強(qiáng)調(diào),編寫Scala代碼的過程是“fun”的。并通過一個(gè)Map的例子,作者展示了,Scala具備類型推導(dǎo)能力和易用的API。

var captial = Map("US" -> "Washington", "France" -> "Paris")
captial += ("Japan" -> "Tokyo")
println(captial("France"))

比如,不需要多余的分號,不需要聲明Map<String, String>這樣的類型,初始化時(shí)可以直接設(shè)置2個(gè)鍵值對進(jìn)去,而不用單獨(dú)調(diào)用put方法。

對比一下原生Java的寫法:

Map<String, String> captial = new HashMap<>();
captial.put("US", "Washington");
captial.put("France", "Paris");

captial.put("Japan", "Tokyo");
System.out.println(captial.get("France"));

2022-01-03

今天讀的這一節(jié)的小標(biāo)題是“A language that grows on you”,翻譯過來應(yīng)該是“一門會(huì)讓你慢慢愛上的語言”。

接下來作者用兩個(gè)例子,印證了這句話。這兩個(gè)例子分別是,可自定義的數(shù)據(jù)類型(以BigInt為例)和可自定義的控制結(jié)構(gòu)(以Akka的API為例)。在一番code show之后,作者都要表達(dá)一下“這些并不是語言內(nèi)建(build-in)的特性,但是用起來和內(nèi)建的沒有區(qū)別”。

過程中,還引用了Eric Raymond《大教堂與集市》的說法,表達(dá)了Scala的設(shè)計(jì)哲學(xué)更接近于集市。

到這里,作者想表達(dá)的意思是比較明確的:Scala是一門內(nèi)核極其精煉的語言,但卻有著非常高的可拓展性。就好像集市一樣,它并沒有(也不可能)事先規(guī)劃好所有的內(nèi)容,但卻擁有極強(qiáng)的演化能力。

2022-01-04

今天讀的這一小節(jié)的標(biāo)題是:“What makes Scala scalable?”。對于之前提到的面向?qū)ο蠹昂瘮?shù)式編程再次做了個(gè)補(bǔ)充,并且可以說是干貨滿滿。

首先作者講了,Scala是OO的。而OO的本質(zhì)是把數(shù)據(jù)和操作封裝在一個(gè)容器中,這個(gè)容器就叫做Object。這樣,操作變成了一種數(shù)據(jù),容器本身也作為數(shù)據(jù)可以被傳來傳去。

同時(shí),作者舉了例子,說有些語言不是那么“純”的OO,比如在Java里面,原始類型就不是對象(數(shù)組也不是),同時(shí),Java還允許在class中定義靜態(tài)的字段和方法。而在Scala中,任何的值本質(zhì)上都是對象,甚至與1 + 2也是,它的底層是針對1這個(gè)Int調(diào)用了+這個(gè)方法,同時(shí)傳入了參數(shù)2。

作者舉的第二個(gè)例子是關(guān)于trait。它有點(diǎn)像Java中的接口,但是擁有自己的字段和方法實(shí)現(xiàn)。關(guān)于trait其實(shí)一直有一些困惑,希望后面的章節(jié)可以解答。

接著作者又講到Scala是函數(shù)式的。這一段堪稱“教科書”式的介紹。

作者介紹了,函數(shù)式編程的2個(gè)特點(diǎn)。函數(shù)是一等公民、函數(shù)調(diào)用需要做到引用透明,也就是沒有副作用。

一等公民是指,函數(shù)可以在任意地方被定義(比如在一個(gè)函數(shù)里),還可以像數(shù)值一樣作為入?yún)⒒蛘叻祷兀ǜ唠A函數(shù))。

那什么叫有副作用的函數(shù)調(diào)用。包括:

打印日志、修改了函數(shù)的入?yún)ⅰ暮瘮?shù)參數(shù)外(全局變量、bean、threadlocal)獲取數(shù)據(jù)、拋出異常等等。對應(yīng)的,一個(gè)沒有副作用的,引用透明的函數(shù),只有輸入/輸出,并且輸出可以被等價(jià)替換掉。

比如1+sum(1, 1)中,sum函數(shù)如果可以直接用2替換,就說明是引用透明的。

2022-01-05

今天的這一小節(jié),大標(biāo)題是“Why Scala?”,作者從兼容性、簡潔程度、高級抽象(?)和靜態(tài)類型4個(gè)方面講了,為什么要選擇Scala。

第一部分是兼容性,除了前面提到的互操作性之外,還強(qiáng)調(diào)了,像底層的String、Int之類的都是復(fù)用的Java原生的類型。同時(shí),Scala還通過一個(gè)叫“隱式轉(zhuǎn)換”的概念,在不修改這些類源碼的情況下,對這些類做了增強(qiáng)。

第二部分是簡潔性。這個(gè)不多說了,差不多是同樣功能的Java的代碼量的1/4吧。后面又介紹了“trait”這個(gè)留到后面展開。

2022-01-08

中間的記錄斷了兩天,不過問題不大。書還是在看的。

這一段,作者提到Scala是“high-level”的,實(shí)際上,作者想強(qiáng)調(diào)的依然是“函數(shù)式”編程范式帶來的好處。

(正本清源)

函數(shù)式編程和面向?qū)ο缶幊滩⒉粵_突,用Java照樣可以寫出非常函數(shù)式的代碼。

真正不太兼容的其實(shí)是描述式的風(fēng)格和命令式的風(fēng)格。

比如給定一個(gè)Int列表,求各元素之和。如果用命令式風(fēng)格來寫,起手一個(gè)sum = 0,后接一個(gè)i = 0,最后for循環(huán)收尾。

如果用描述式風(fēng)格來寫,“一個(gè)列表的各個(gè)元素之和” 等于 “列表的第一個(gè)元素” 加上 “列表其余元素構(gòu)成的列表的各個(gè)元素之和”。

假設(shè)取列表的第一個(gè)元素用head表示,取列表的剩余元素構(gòu)成的列表用tail表示。則偽代碼如下:

sum(alist) = {
if(alist != empty) return 0
else return head(alist) + sum(tail(alist))
}

可以看到,描述式風(fēng)格和遞歸是比較自然的一對組合,無怪于FP系的語言都喜歡用遞歸。

遵循函數(shù)式風(fēng)格的另一個(gè)好處,是可以寫出“引用透明”的函數(shù)。也就是說,這個(gè)函數(shù)調(diào)用的結(jié)果,可以用該函數(shù)的返回值等價(jià)的替換掉。

比如代碼里有一段是:a + sum(b, c),如果b和c分別等于2和3的話,那么和直接寫a + 5是等價(jià)的?;蛟S因?yàn)檫@個(gè)例子太簡單了,并且是數(shù)值計(jì)算,所以大部分人可以天然的寫出這種引用透明的函數(shù)。

但,如果是一段業(yè)務(wù)代碼,你能保證不在函數(shù)里調(diào)用Spring的bean去讀寫數(shù)據(jù)庫嗎?能保證不修改某個(gè)全局變量或者ThreadLocal嗎?能保證不去調(diào)用某個(gè)入?yún)⒌膕et方法嗎?

引用透明的另一種說法是“沒有副作用”,以上列的一些例子都是副作用的體現(xiàn)。

沒有副作用的代碼,易于測試和重構(gòu),也更少的引入bug。想想是不是經(jīng)常發(fā)現(xiàn)某個(gè)字段,在經(jīng)歷了一系列的函數(shù)調(diào)用之后,不知道什么時(shí)候就被設(shè)置了一個(gè)不太符合預(yù)期的值,然后引出一系列莫名其妙的問題?

上面提到的最后一個(gè)例子,有一個(gè)專門的名字,叫aliasing problem,那么函數(shù)式的編程風(fēng)格,是如何避免這樣的問題呢?答案就是使用不可變的數(shù)據(jù)(immutable data)。

通過把一個(gè)類所有的字段都設(shè)置成final的,這個(gè)類就是一個(gè)不可變的類(如果有個(gè)字段是final的Map,非要去修改這個(gè)Map,就屬于硬杠了)。

相應(yīng)的,函數(shù)體里面也不再能set各種字段。一旦要改變些什么,要應(yīng)該通過返回一個(gè)新的類的實(shí)例完成。

實(shí)際上對于大多數(shù)程序員來說,疑問都是,不可變的數(shù)據(jù),能編程嗎?看看Java的String,看看Spark的RDD。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容