寫給小師妹的Scala學習筆記·二

第二周

這一周的內(nèi)容主要圍繞著“函數(shù)”來進行璧尸。本來想順著書的內(nèi)容往下講咒林,不過那樣就沒有自己的東西了,所以爷光,就想到哪里寫到哪里吧~

普通函數(shù)

我們從一個最普通的函數(shù)開始垫竞。它的作用跟函數(shù)名一樣,你給它輸入什么蛀序,它給你返回什么欢瞪。

def echo(x : Int) : Int = {
    x
}

在語法上:

  1. 函數(shù)定義以def開頭。
  2. 函數(shù)的返回值類型在函數(shù)名之后徐裸。
  3. 注意那個等號遣鼓。
  4. 不需要顯式的寫return。(請問是為什么呢)

泛型函數(shù)

當然倦逐,這個例子只是為了引出接下來的例子譬正,因為普通的版本,只能接受Int作為輸入和輸入檬姥,如果要支持任意類型曾我,就需要讓函數(shù)支持泛型。

def echo[T](x : T) : T = {
    x
}

通過在函數(shù)名之后的中括號和T健民,可以支持單個參數(shù)的泛型抒巢,如果要支持多個,可以寫成類似[T, V]的形式秉犹。

帶默認值的函數(shù)

好了蛉谜,水完跟Java類似的部分,下面講講Scala做得好的部分崇堵。

相信你一定見過這樣的代碼型诚,把一個字符串類型的日期,轉換為Date鸳劳,通常會根據(jù)不同的合適進行適配狰贯,比如20201226和2020-12-26。通常大部分日期格式都是第二種赏廓,因此會定義2個函數(shù)涵紊,一個需要str和pattern 2個參數(shù),一個只需要str一個參數(shù)幔摸。

但是摸柄,在Scala中,你可以通過一個帶默認值的函數(shù)來完成Java里2個函數(shù)的功能既忆。

def str2Date(str : String, pattern : String = "yyyy-MM-dd") : Date = {
    …
} 

str2Date("20201226", "yyyy-MM-dd")

str2Date("2020-12-26")


在語法上驱负,通過pattern參數(shù)后面加一個等號來完成嗦玖。

遞歸和尾遞歸

遞歸大家都懂,但很多人都不知道尾遞歸是什么电媳。

在我的理解里踏揣,尾遞歸就是滿足這樣條件的遞歸:遞歸函數(shù)的調(diào)用出現(xiàn)在函數(shù)的最后一行,并且匾乓,這一行有且只有這個函數(shù)調(diào)用本身捞稿。

比如下面的寫法就是一個普通的遞歸函數(shù)。

def fact(i : Int) : Int = {
    if (i == 1) 1
    else i * fact(i - 1)
}

如果要改成尾遞歸形式拼缝,可以這么寫娱局。

def fact(i : Int) : Int = {
    @annotation.tailrec
    def inner(n : Int, result : Int) : Int = { // 1
        if (n == 1) result                     // 2
        else inner(n - 1, n * result)          // 3
    }                                          // 4
    inner(i, 1)                                // 5
}

這里用了一個嵌套函數(shù)的例子,證明了在Scala中函數(shù)屬于一等公民咧七。(這個點我們稍后再展開)

一次函數(shù)調(diào)用意味著JVM中一個棧幀的入棧和出棧衰齐,因此普通的遞歸函數(shù)容易出現(xiàn)“爆棧”继阻。而編譯器在看到尾遞歸的寫法的時候耻涛,可以進行優(yōu)化:把遞歸改為迭代,從而消除函數(shù)調(diào)用瘟檩。

從遞歸到迭代是有范式的抹缕,比如上面的寫法可以轉化為以下的等價寫法,不過墨辛,這是我的一個理解卓研,而真正的實現(xiàn),感覺可以再留一個坑在這里后面來填睹簇。

def fact(i : Int) : Int = {
    var result : Int = 1                            // 根據(jù)5的第二個參數(shù)
    var n : Int = i                                 // 根據(jù)5的第一個參數(shù)
    while (n > 1) {                                 // 根據(jù)2和3
      result *= n                                   // 根據(jù)3的n * result
      n -= 1                                        // 根據(jù)3的n - 1
    }
    result
}

(順帶提一句奏赘,在Java里是沒有尾遞歸優(yōu)化的說法的)

下面終于來到了FP(函數(shù)式編程)的世界(激動)

一等公民

先叨叨一下什么是剛才說到的一等公民。在Java的世界里太惠,類是一等公民磨淌,因為你可以把它賦值給一個變量、你可以把它作為一個方法的輸入和輸出凿渊、你可以在任何地方定義一個類(在方法里伦糯、在類里、甚至是定義一個匿名內(nèi)部類)嗽元。在Java的世界里,函數(shù)是“從屬”于類的喂击,這就會顯得很繁瑣和臃腫剂癌,比如要根據(jù)不同的方式給一個List排序,還需要定義一個排序的接口翰绊。

而在Scala的世界(或者說函數(shù)式的世界)佩谷,函數(shù)就是一等公民旁壮,我們參考一下剛才的定義,你可以把它賦值給一個變量谐檀、你可以把它作為一個方法的輸入和輸出抡谐、你可以在任何地方定義一個函數(shù)(比如剛才那個例子里面的inner就是定義在fact里面的輔助函數(shù))。

當我們用i這個變量桐猬,val i : Int = 1麦撵,去接收一個值的時候,實際上我們需要聲明i的類型是什么溃肪。而現(xiàn)在免胃,要支持把函數(shù)賦值給i這樣的特性,對于程序員來說是有“額外”的負擔的惫撰。那就是你需要知道函數(shù)的類型羔沙,并把它顯式的聲明出來。

在寫Java的時候厨钻,其實你不會那么的關注一個方法的類型扼雏。比如剛才的fact函數(shù),類型是(Int) => Int夯膀,而inner函數(shù)的類型是(Int, Int) => Int诗充。可以看到棍郎,函數(shù)的類型和入?yún)⒌念愋图皞€數(shù)其障,以及返回值都相關。(這和Java里判斷方法是否重載的邏輯不同)涂佃。

(考考你励翼,Int => Int => Int,代表的是怎樣的一個函數(shù)辜荠?)

高階函數(shù)

剛才講了函數(shù)賦值給變量i的場景汽抚,那相應的,一個函數(shù)A可以作為另一個函數(shù)B的入?yún)⒒蛘叻祷刂挡 O馚這樣的函數(shù)造烁,我們稱為高階函數(shù)。

大家最耳熟能詳?shù)母唠A函數(shù)應該是Map和Reduce午笛。如果是從Java或者Spark切換過來的惭蟋,可能對于Map的理解是Stream的一個方法或者RDD的一個方法。實際上最正統(tǒng)的Map(來自于Lisp之父約翰·麥卡錫)药磺,應該支持2個參數(shù)告组,一個參數(shù)是一個集合,另一個參數(shù)是一個函數(shù)癌佩。它的作用是把函數(shù)應用在每一個集合元素上木缝,并返回一個新的集合便锨。


// 定義一個map函數(shù)
def map(f : Int => Int, c : List[Int]) : List[Int] = {
  var res = List[Int]()
  for(e <- c) {
    res = res :+ f(e)
  }
  res
}

// 將列表的每個元素值翻倍
map(e => e * 2, List(1, 2, 3, 4))

// 將列表的每個元素值加1
map(e => e + 1, List(1, 2, 3, 4))

基于高階函數(shù),可以實現(xiàn)Java中很多的設計模式我碟,比如在高階函數(shù)中定義好一個通用的骨架放案,然后把需要動態(tài)變化的部分,通過函數(shù)參數(shù)傳進來矫俺,能實現(xiàn)類似于模版模式吱殉、策略模式、代理模式等多種效果恳守。

多扯幾句考婴,高階函數(shù)這個詞,聽著比較學究催烘,實際上沥阱,如果把上面那個例子的=>改成->就是Java中的Lambda表達式的寫法了,從使用的角度來說伊群,我們不需要高階函數(shù)這樣的說法考杉,不過如果在面試時候能把這一套講清楚,無疑是一個加分項舰始。

更多關于Java中的Lambda的內(nèi)容崇棠,可以參考《State of the Lambda》[2]這篇文檔(需要的化,可以翻譯給你哦)丸卷。

柯里化

上面講了以函數(shù)作為參數(shù)的高階函數(shù)枕稀,下面我們講講以函數(shù)作為返回值的高階函數(shù)。

假設有這樣一個東西(A):

當我輸入1時谜嫉,它會返回給我這樣一個東西(B)萎坷,接受一個Int型的輸入,并返回輸入加1后的結果沐兰。

當我輸入10時哆档,它會返回給我這樣一個東西(C),接受一個Int型的輸入住闯,并返回輸入加10后的結果瓜浸。

在Java的世界里,我們可能會定義一個叫Increase接口比原,B和C都是它的實現(xiàn)類插佛。,而A則是Increase的工廠量窘。

一個字雇寇,繁瑣。在FP的世界里,我們Duck不必這樣谢床。

def increaseMaker(i : Int)(n : Int) : Int = {
    i + n
}

increaseMaker(5)(10)
// 得到15

val increase1 = increaseMaker(1) _
val increase5 = increaseMaker(5) _

increase1(10)
// 得到11

increase5(10)
// 得到15

定義一個構造increase的函數(shù),它有2個參數(shù)厘线。

通過increaseMaker(1) _我們綁定了它的第一個參數(shù)识腿,在第二個參數(shù)的位置,用占位符代替造壮,代表未來再來綁定渡讼。這樣我們就構造了一個部分應用的函數(shù)。

再通過類似increase1(10)的形式耳璧,拿到最后的結果成箫。

這底層所用到的技術,我們稱之為柯里化旨枯。它是指蹬昌,一個多參數(shù)的函數(shù),可以接受一個參數(shù)攀隔,并返回一個可以接受其余參數(shù)的函數(shù)的技術皂贩。

而返回的那個函數(shù),我們成為閉包昆汹。意思是明刷,一個攜帶著狀態(tài)的函數(shù),而這個狀態(tài)或者說變量的定義满粗,來自于函數(shù)之外辈末。

剛才那個例子,實際上還可以這么寫映皆,我認為參數(shù)組挤聘,就是下面這種寫法的語法糖:

def increaseMaker(i : Int) : Int => Int = {
    def inner(n : Int) = {
        i + n
    }
    inner
}

因為它,像Lambda演算這樣只能支持單參數(shù)的東西劫扒,才有了支持多參數(shù)的能力檬洞。

插播一下,柯里化這個詞沟饥,是為了紀念數(shù)學家柯里而命名的添怔。他的全名叫Haskell Curry,first name和last name都貢獻給了PL界贤旷。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末广料,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子幼驶,更是在濱河造成了極大的恐慌艾杏,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盅藻,死亡現(xiàn)場離奇詭異购桑,居然都是意外死亡畅铭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門勃蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硕噩,“玉大人,你說我怎么就攤上這事缭贡÷茫” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵阳惹,是天一觀的道長谍失。 經(jīng)常有香客問我,道長莹汤,這世上最難降的妖魔是什么快鱼? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮体啰,結果婚禮上攒巍,老公的妹妹穿的比我還像新娘。我一直安慰自己荒勇,他們只是感情好柒莉,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沽翔,像睡著了一般兢孝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仅偎,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天跨蟹,我揣著相機與錄音,去河邊找鬼橘沥。 笑死窗轩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的座咆。 我是一名探鬼主播痢艺,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼介陶!你這毒婦竟也來了堤舒?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哺呜,失蹤者是張志新(化名)和其女友劉穎舌缤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡国撵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年陵吸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片介牙。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡走越,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耻瑟,到底是詐尸還是另有隱情,我是刑警寧澤赏酥,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布喳整,位于F島的核電站,受9級特大地震影響裸扶,放射性物質(zhì)發(fā)生泄漏框都。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一呵晨、第九天 我趴在偏房一處隱蔽的房頂上張望魏保。 院中可真熱鬧,春花似錦摸屠、人聲如沸谓罗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檩咱。三九已至,卻和暖如春胯舷,著一層夾襖步出監(jiān)牢的瞬間刻蚯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工桑嘶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炊汹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓逃顶,卻偏偏與公主長得像讨便,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子口蝠,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容