Scala函數(shù)式編程之一——編程范式

本節(jié)的內容的有以下幾點:
一树肃、編程范式以及為什么要使用函數(shù)式編程?
二嘴瓤、什么是函數(shù)式編程
三、函數(shù)式編程的特征

一莉钙、編程范式以及為什么要使用函數(shù)式編程廓脆?

1、編程范式

我想大家應該在平時工作過程中磁玉,也許會因為項目而去另外學習或適應一種自己之前完全不熟悉的編程語言停忿,對有著一定編程經驗并已經熟練掌握一門語言的人來說,快速上手一門語言并應用于項目中也許并不是一件很困難的事情蚊伞。但是情況并非總是如此席赂,跨語言對一個程序員來說影響也許不是最大的,但是編程范式的變更也許會讓一個程序員好一會都緩不過神來时迫。

編程范式與編程語言不同颅停,它很深層更內在,它是編程思想的凝練掠拳,通過編程語言的體現(xiàn)出來癞揉,又通過實踐內化為程序員的一種編程思維。它并不容易在短時間內融匯貫通,而需要通過大量地實踐加深對這種編程方式的理解喊熟。

我們常見的主流編程思維有三種:
1柏肪、邏輯式編程
2、命令式編程
3芥牌、函數(shù)式編程

三種編程范式都體現(xiàn)了各自獨特的對用程序解決問題的思考烦味。
1、邏輯式編程不注重解決問題的步驟壁拉,而是注重邏輯谬俄。它設定答案須符合的規(guī)則來解決問題,而非設定步驟來解決問題:規(guī)則+事實=結果扇商。利用它編寫的程序不是由指令序列組成凤瘦,而是由一系列公理或定義對象之間關系的規(guī)則組。
2案铺、命令式編程關心解決問題的步驟蔬芥。它需要我們制定好對應解決某問題的一系列步驟,且讓程序嚴格按照步驟去執(zhí)行控汉。它編寫的程序需要我們去考慮在編碼范圍內需要考慮的一切問題笔诵,包括性能,邊界驗證姑子,資源回收等乎婿。
3、函數(shù)式編程關心的是數(shù)據(jù)的映射街佑,它重視更高層面上數(shù)據(jù)集之間的變換關系谢翎,而不是編制程序執(zhí)行的每一步笆搓。它在機器學習算法高度發(fā)展的今天午阵,變成了一種算法實現(xiàn)的主要編程范式之一胀莹。它的思維方式是將數(shù)據(jù)集的變換和數(shù)據(jù)上計算邏輯組合起來產生結果惊完。編碼者不需要過多關心數(shù)據(jù)集中每個元素的具體變換步驟报咳,只需要在數(shù)據(jù)集合上組織計算邏輯并觸發(fā)計算朋其。

二膝舅、什么是函數(shù)式編程

正如上面提到的姨伟,函數(shù)式編程是一種面向數(shù)據(jù)映射的編程范式谊迄,它的目標是使用純凈的函數(shù)來表達問題的解決方式闷供。

所謂函數(shù)式編程的函數(shù)本質,并不是指我們編程語言中的函數(shù)(例如python的def之類的)统诺,而是數(shù)學中的函數(shù)映射歪脏,這種映射只是接受參數(shù),并得到一個結果粮呢,它并不會對外界產生任何影響唾糯,這樣的一個函數(shù)的好處非常多怠硼,它們更有利于模塊化,因此更容易測試移怯、復用香璃、并行化、泛化以及推導舟误。

我們可以把函數(shù)對外界產生的影響稱之為副作用葡秒,下面一個例子來說明,帶有副作用的函數(shù)是如何造成困擾的嵌溢。

一個簡單的副作用例子

我們?yōu)橥婢叩曩徺I玩具來編寫一段程序眯牧,程序目的是購買一個玩具,并在信用卡上扣費

class Shop{
  def buyToy(cc: CreditCart): Toy = {
    val toy = Toy()
    cc.charge(toy.price) // 副作用的源頭
    toy
  }
}
class CreditCart{
  // deduct
  def charge(price: Double) = ???
}
case class Toy(val price: Double = 10)

cc.charge(toy.price)就是副作用的源頭赖草,因為信用卡的計費可能會涉及到外部世界的一系列交互学少,我們的函數(shù)只不過想要返回一個玩具,而其它額外的行為也隨之發(fā)生了秧骑,這就是副作用版确。

這樣的副作用導致很難進行測試,因為我們不希望我們的測試方法真的去走一遍信用卡和外部交互的流程乎折。這種對可測試性的修改就意味著設計的修改:按理說CreditCard不應該知道如何去跟信用卡公司去進行實際扣費和持久化計費到內部系統(tǒng)中绒疗,我們可以讓CreditCard忽略這件事,通過一個Payments接口骂澄,與外部交互的邏輯都托管給這個實現(xiàn)這個Payments的對象吓蘑,然后分別實現(xiàn)一個為真正執(zhí)行計費邏輯的Payments和一個用于測試的MockPayments。這樣的做法使得模塊更加模塊化和可測試坟冲。

class Shop{
  def buyToy(cc: CreditCart, p: Payments): Toy = {
    val toy = Toy()
    p.charge(cc, toy.price)
    toy
  }
}
trait Payments{
  def charge(cc: CreditCart, price: Double)
}

我們這里再考慮一個問題:buyToy方法很難復用磨镶!例如一個客戶想要購買20個玩具,最理想的是復用這個方法健提,調用20次進行扣費琳猫,不管是從實際意義上的手續(xù)費角度,還是從支付系統(tǒng)的調用的性能方面都有十分不理的影響矩桂。當然沸移,我們還可以使用一個新的方法buyToys去實現(xiàn)痪伦,那么重復的代碼邏輯會很多侄榴,而且會失去代碼復用性和組合性。

去除副作用

函數(shù)式的解決方案就是去除副作用网沾,我們可以不需要在買玩具的時候把扣費的邏輯執(zhí)行了癞蚕,可以把這個費用本身和玩具一起返回,我們再來改造一下代碼:

class Shop{
  def buyToy(cc: CreditCart): (Toy, Charge) = {
    val toy = Toy()
    (toy, Charge(cc, toy.price))
  }
}
case class Charge(cc: CreditCart, amount: Double){
  def combine(other: Charge): Charge = {
    if(cc == other.cc)
      Charge(cc, amount + other.amount)
    else
      throw new RuntimeException("不允許不同信用卡扣費")
  }
}

在這段執(zhí)行邏輯中辉哥,我們并沒有在buyToy的方法中進行任何結算費用的操作桦山,而只是返回物品本身和它的費用攒射,我們希望的是把副作用剝離到更外層,而不是在函數(shù)調用的過程中進行恒水,那么我們的結算多個Toy的動作也就更好完成了会放。

def buyToys(cc: CreditCart, n: Int): (List[Toy], Charge) = {
    val purchases : List[(Toy, Charge)] = List.fill(n)(buyToy(cc))
    
    // List[(A, B)] => (List[A], List[B])
    val (toys, charges) = purchases.unzip
    (toys, charges.reduceLeft(_.combine(_))) // 合并消費
  }

現(xiàn)在我們可以把購買玩具的邏輯和付賬邏輯隔離開,并可以復用代碼實現(xiàn)多個玩具的購買钉凌。
相比之前使用Payments接口而言咧最,我們使用Charge作為一等值的來隔離副作用,將
購買->付賬(副作用)->得到玩具
的邏輯轉變?yōu)?br> (購買->得到賬單(可合并)->得到玩具)*->賬單一并結賬(副作用)

我們可以自己實現(xiàn)一個Payments對象在最后結算Charge里的price御雕,但是Toy類并不需要了解它矢沿。

買玩具小結

我們在這個例子中看到如何把計費的創(chuàng)建過程與實際的處理過程進行分離∷岣伲總的來說捣鲸,就是把這些副作用推到程序的外層,來轉化任何帶有副作用的函數(shù)闽坡。對于優(yōu)秀的函數(shù)式編程者來說栽惶,程序的實現(xiàn)就是一層純的內核和一層很薄的外圍來處理副作用。

三无午、函數(shù)式編程的特征

純函數(shù)

我們在前面提到過純函數(shù)的這一概念媒役,這里給出它的精確定義:如果一個函數(shù)在程序執(zhí)行的過程中出了根據(jù)輸入?yún)?shù)給出結果之外,對外界沒有任何其它的影響宪迟,那么可以說這一類函數(shù)是沒有副作用的酣衷,這類函數(shù)也稱為純函數(shù)。

例如Scala中1 + 2(+實際上是一個中置操作符次泽,可以被改寫為1.+(2))穿仪,那么函數(shù)+只接受2為參數(shù),然后與1相加返回一個新的整型3意荤,整個過程沒有引入到除了參數(shù)和調用者外的任意一個外界變化啊片。

引用透明和替代模型

純函數(shù)為函數(shù)式編程帶來的一個好處就是:純函數(shù)更容易推理,這就使得我們程序執(zhí)行的推導過程更為流暢和自然玖像。我們需要走到更高的層次去看看這些好處是怎么來的紫谷。

(為了敘述下面的內容,我們先來說明一下編碼層次:函數(shù)<表達式<程序捐寥。)

我們上升至表達式的領域來:對于1 + 2這個表達式笤昨,它在任何一個地方都可以被它的結果3直接取代而不會引起程序的任何變更,我們稱之為引用透明(表達式層面上的)握恳。當調用一個函數(shù)時傳入的表達式是引用透明的瞒窒,并且函數(shù)的調用也是引用透明的,那么這個函數(shù)就是一個純函數(shù)乡洼。純函數(shù)要求無論進行來任何操作都可以用它的返回值來代替它崇裁,這種限制使得程序的求值可以通過簡單自然的推導得出匕坯,我們稱之為替代模型(程序層面上的)。如果程序中每個表達式都是引用透明的拔稳,那么我們可以使用替代模型來進行等式推理葛峻,就例如我們的代數(shù)方程一般。

替代模型的之所以很容易進行推理巴比,因為它對運算的影響是局部的泞歉,只需要理解局部的計算邏輯,不需要在每一個表達式執(zhí)行過程中都縱觀全局的變化匿辩,對于程序的執(zhí)行可如對代數(shù)推理一般流暢而自然地進行腰耙。它使得程序進行模塊化變得十分簡單而清晰,而模塊化的函數(shù)更容易被測試和進一步的組合铲球,提供程序的整體質量挺庞。

小結

總的來說,函數(shù)式編程的相對于其它編程范式來說有著它獨特的優(yōu)勢稼病,尤其是對于我熟知的命令式范式來說选侨,它展現(xiàn)了一種完全不同的編程思維。在本章筆者也有一些對問題的思考:
去除了副作用之后然走,所有問題的都有一套函數(shù)式的編程方案嘛援制?
筆者認為,Scala在意的是芍瑞,如何進行函數(shù)式編程晨仑,并非所有問題的最佳方案都是使用函數(shù)式編程范式解決,函數(shù)式范式有自己的適用場景拆檬。
引用:
https://www.zhihu.com/question/28292740
《Scala函數(shù)式編程》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末洪己,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子竟贯,更是在濱河造成了極大的恐慌答捕,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屑那,死亡現(xiàn)場離奇詭異拱镐,居然都是意外死亡,警方通過查閱死者的電腦和手機持际,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門沃琅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人选酗,你說我怎么就攤上這事阵难≡兰希” “怎么了芒填?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵呜叫,是天一觀的道長。 經常有香客問我殿衰,道長朱庆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任闷祥,我火速辦了婚禮娱颊,結果婚禮上,老公的妹妹穿的比我還像新娘凯砍。我一直安慰自己箱硕,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布悟衩。 她就那樣靜靜地躺著剧罩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪座泳。 梳的紋絲不亂的頭發(fā)上惠昔,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音挑势,去河邊找鬼镇防。 笑死,一個胖子當著我的面吹牛潮饱,可吹牛的內容都是我干的来氧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼香拉,長吁一口氣:“原來是場噩夢啊……” “哼饲漾!你這毒婦竟也來了?” 一聲冷哼從身側響起缕溉,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤考传,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后证鸥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僚楞,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年枉层,在試婚紗的時候發(fā)現(xiàn)自己被綠了泉褐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸟蜡,死狀恐怖膜赃,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情揉忘,我是刑警寧澤跳座,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布端铛,位于F島的核電站,受9級特大地震影響疲眷,放射性物質發(fā)生泄漏禾蚕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一狂丝、第九天 我趴在偏房一處隱蔽的房頂上張望换淆。 院中可真熱鬧,春花似錦几颜、人聲如沸倍试。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽易猫。三九已至,卻和暖如春具壮,著一層夾襖步出監(jiān)牢的瞬間准颓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工棺妓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留攘已,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓怜跑,卻偏偏與公主長得像样勃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子性芬,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容