每天兩頁(yè),分享《Programming in Scala》的心得噪服。
這個(gè)系列之前其實(shí)寫(xiě)過(guò)。具體可以看:
寫(xiě)給小師妹的Scala學(xué)習(xí)筆記·開(kāi)篇
寫(xiě)給小師妹的Scala學(xué)習(xí)筆記·二
當(dāng)時(shí)是為了快速“學(xué)會(huì)”Scala霜医,選擇了疾風(fēng)式的搞法事镣,幾周就擼完一本書(shū)。
這次我們慢下來(lái)古劲,找一本Scala作者自己寫(xiě)的書(shū)斥赋,仔細(xì)品一品Scala的設(shè)計(jì)哲學(xué),并且以日記的形式記錄一下所思所想产艾。
以下是正文部分疤剑。
2022-01-02
今天讀的這一章的大標(biāo)題叫做“ A Scalable Language”。Scala這個(gè)名字闷堡,源于作者希望創(chuàng)造一種Scala-ble的語(yǔ)言隘膘。
接下來(lái),他簡(jiǎn)要的介紹了Scala的幾個(gè)特性:
首先杠览,它是一種運(yùn)行在JVM上的語(yǔ)言弯菊,因此和Java具有“無(wú)縫”的交操作性。借助于Java強(qiáng)大的生態(tài)倦零,可以少造無(wú)數(shù)輪子误续。當(dāng)然像Groovy、Clojure等等都有這樣的特性扫茅。
其次蹋嵌,Scala兼有面向?qū)ο蠛秃瘮?shù)式2種編程范式(這一點(diǎn)其實(shí)是相當(dāng)不常見(jiàn)的),而且是一門(mén)靜態(tài)類(lèi)型的語(yǔ)言(所謂動(dòng)態(tài)一時(shí)爽葫隙,重構(gòu)火葬場(chǎng)栽烂,靜態(tài)語(yǔ)言,可以讓編譯器替你干不少臟活累活)恋脚。函數(shù)式的一面使得它易于構(gòu)建小組件腺办,面向?qū)ο笫沟盟梢杂脕?lái)構(gòu)建大型程序。
接下來(lái)作者強(qiáng)調(diào)糟描,編寫(xiě)Scala代碼的過(guò)程是“fun”的怀喉。并通過(guò)一個(gè)Map的例子,作者展示了船响,Scala具備類(lèi)型推導(dǎo)能力和易用的API躬拢。
var captial = Map("US" -> "Washington", "France" -> "Paris")
captial += ("Japan" -> "Tokyo")
println(captial("France"))
比如躲履,不需要多余的分號(hào),不需要聲明Map<String, String>這樣的類(lèi)型聊闯,初始化時(shí)可以直接設(shè)置2個(gè)鍵值對(duì)進(jìn)去工猜,而不用單獨(dú)調(diào)用put方法。
對(duì)比一下原生Java的寫(xiě)法:
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”菱蔬,翻譯過(guò)來(lái)應(yīng)該是“一門(mén)會(huì)讓你慢慢愛(ài)上的語(yǔ)言”篷帅。
接下來(lái)作者用兩個(gè)例子,印證了這句話(huà)拴泌。這兩個(gè)例子分別是魏身,可自定義的數(shù)據(jù)類(lèi)型(以BigInt為例)和可自定義的控制結(jié)構(gòu)(以Akka的API為例)。在一番code show之后弛针,作者都要表達(dá)一下“這些并不是語(yǔ)言?xún)?nèi)建(build-in)的特性叠骑,但是用起來(lái)和內(nèi)建的沒(méi)有區(qū)別”李皇。
過(guò)程中削茁,還引用了Eric Raymond《大教堂與集市》的說(shuō)法,表達(dá)了Scala的設(shè)計(jì)哲學(xué)更接近于集市掉房。
到這里茧跋,作者想表達(dá)的意思是比較明確的:Scala是一門(mén)內(nèi)核極其精煉的語(yǔ)言,但卻有著非常高的可拓展性卓囚。就好像集市一樣瘾杭,它并沒(méi)有(也不可能)事先規(guī)劃好所有的內(nèi)容,但卻擁有極強(qiáng)的演化能力哪亿。
2022-01-04
今天讀的這一小節(jié)的標(biāo)題是:“What makes Scala scalable?”粥烁。對(duì)于之前提到的面向?qū)ο蠹昂瘮?shù)式編程再次做了個(gè)補(bǔ)充,并且可以說(shuō)是干貨滿(mǎn)滿(mǎn)蝇棉。
首先作者講了讨阻,Scala是OO的。而OO的本質(zhì)是把數(shù)據(jù)和操作封裝在一個(gè)容器中篡殷,這個(gè)容器就叫做Object钝吮。這樣,操作變成了一種數(shù)據(jù)板辽,容器本身也作為數(shù)據(jù)可以被傳來(lái)傳去奇瘦。
同時(shí),作者舉了例子劲弦,說(shuō)有些語(yǔ)言不是那么“純”的OO耳标,比如在Java里面,原始類(lèi)型就不是對(duì)象(數(shù)組也不是)邑跪,同時(shí)次坡,Java還允許在class中定義靜態(tài)的字段和方法纲仍。而在Scala中,任何的值本質(zhì)上都是對(duì)象贸毕,甚至與1 + 2也是郑叠,它的底層是針對(duì)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ù)式的兴蒸。這一段堪稱(chēng)“教科書(shū)”式的介紹视粮。
作者介紹了,函數(shù)式編程的2個(gè)特點(diǎn)橙凳。函數(shù)是一等公民蕾殴、函數(shù)調(diào)用需要做到引用透明,也就是沒(méi)有副作用岛啸。
一等公民是指钓觉,函數(shù)可以在任意地方被定義(比如在一個(gè)函數(shù)里),還可以像數(shù)值一樣作為入?yún)⒒蛘叻祷兀ǜ唠A函數(shù))坚踩。
那什么叫有副作用的函數(shù)調(diào)用荡灾。包括:
打印日志、修改了函數(shù)的入?yún)⑺仓暮瘮?shù)參數(shù)外(全局變量批幌、bean、threadlocal)獲取數(shù)據(jù)嗓节、拋出異常等等荧缘。對(duì)應(yīng)的,一個(gè)沒(méi)有副作用的赦政,引用透明的函數(shù)胜宇,只有輸入/輸出,并且輸出可以被等價(jià)替換掉恢着。
比如1+sum(1, 1)中桐愉,sum函數(shù)如果可以直接用2替換,就說(shuō)明是引用透明的掰派。
2022-01-05
今天的這一小節(jié)从诲,大標(biāo)題是“Why Scala?”靡羡,作者從兼容性系洛、簡(jiǎn)潔程度俊性、高級(jí)抽象(?)和靜態(tài)類(lèi)型4個(gè)方面講了描扯,為什么要選擇Scala定页。
第一部分是兼容性,除了前面提到的互操作性之外绽诚,還強(qiáng)調(diào)了典徊,像底層的String、Int之類(lèi)的都是復(fù)用的Java原生的類(lèi)型恩够。同時(shí)卒落,Scala還通過(guò)一個(gè)叫“隱式轉(zhuǎn)換”的概念,在不修改這些類(lèi)源碼的情況下蜂桶,對(duì)這些類(lèi)做了增強(qiáng)儡毕。
第二部分是簡(jiǎn)潔性。這個(gè)不多說(shuō)了扑媚,差不多是同樣功能的Java的代碼量的1/4吧腰湾。后面又介紹了“trait”這個(gè)留到后面展開(kāi)。
2022-01-08
中間的記錄斷了兩天钦购,不過(guò)問(wèn)題不大檐盟。書(shū)還是在看的。
這一段押桃,作者提到Scala是“high-level”的,實(shí)際上导犹,作者想強(qiáng)調(diào)的依然是“函數(shù)式”編程范式帶來(lái)的好處唱凯。
(正本清源)
函數(shù)式編程和面向?qū)ο缶幊滩⒉粵_突,用Java照樣可以寫(xiě)出非常函數(shù)式的代碼谎痢。
真正不太兼容的其實(shí)是描述式的風(fēng)格和命令式的風(fēng)格磕昼。
比如給定一個(gè)Int列表,求各元素之和节猿。如果用命令式風(fēng)格來(lái)寫(xiě)票从,起手一個(gè)sum = 0,后接一個(gè)i = 0滨嘱,最后for循環(huán)收尾峰鄙。
如果用描述式風(fēng)格來(lái)寫(xiě),“一個(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)格和遞歸是比較自然的一對(duì)組合兜看,無(wú)怪于FP系的語(yǔ)言都喜歡用遞歸。
遵循函數(shù)式風(fēng)格的另一個(gè)好處细移,是可以寫(xiě)出“引用透明”的函數(shù)熊锭。也就是說(shuō)葫哗,這個(gè)函數(shù)調(diào)用的結(jié)果,可以用該函數(shù)的返回值等價(jià)的替換掉球涛。
比如代碼里有一段是:a + sum(b, c)劣针,如果b和c分別等于2和3的話(huà)亿扁,那么和直接寫(xiě)a + 5是等價(jià)的〈幼#或許因?yàn)檫@個(gè)例子太簡(jiǎn)單了襟己,并且是數(shù)值計(jì)算,所以大部分人可以天然的寫(xiě)出這種引用透明的函數(shù)牍陌。
但擎浴,如果是一段業(yè)務(wù)代碼毒涧,你能保證不在函數(shù)里調(diào)用Spring的bean去讀寫(xiě)數(shù)據(jù)庫(kù)嗎?能保證不修改某個(gè)全局變量或者ThreadLocal嗎契讲?能保證不去調(diào)用某個(gè)入?yún)⒌膕et方法嗎?
引用透明的另一種說(shuō)法是“沒(méi)有副作用”唤冈,以上列的一些例子都是副作用的體現(xiàn)银伟。
沒(méi)有副作用的代碼你虹,易于測(cè)試和重構(gòu)彤避,也更少的引入bug。想想是不是經(jīng)常發(fā)現(xiàn)某個(gè)字段挟伙,在經(jīng)歷了一系列的函數(shù)調(diào)用之后,不知道什么時(shí)候就被設(shè)置了一個(gè)不太符合預(yù)期的值尖阔,然后引出一系列莫名其妙的問(wèn)題?
上面提到的最后一個(gè)例子介却,有一個(gè)專(zhuān)門(mén)的名字,叫aliasing problem桂肌,那么函數(shù)式的編程風(fēng)格永淌,是如何避免這樣的問(wèn)題呢?答案就是使用不可變的數(shù)據(jù)(immutable data)遂蛀。
通過(guò)把一個(gè)類(lèi)所有的字段都設(shè)置成final的,這個(gè)類(lèi)就是一個(gè)不可變的類(lèi)(如果有個(gè)字段是final的Map李滴,非要去修改這個(gè)Map,就屬于硬杠了)谆扎。
相應(yīng)的芹助,函數(shù)體里面也不再能set各種字段。一旦要改變些什么周瞎,要應(yīng)該通過(guò)返回一個(gè)新的類(lèi)的實(shí)例完成。
實(shí)際上對(duì)于大多數(shù)程序員來(lái)說(shuō)声诸,疑問(wèn)都是,不可變的數(shù)據(jù)退盯,能編程嗎?看看Java的String渊迁,看看Spark的RDD。