函數(shù)式編程快速入門-學(xué)習(xí)筆記

函數(shù)式編程(FP) 學(xué)習(xí)筆記

  • 同大家分享、交流學(xué)習(xí)成果,觀點(diǎn)不一定正確椿猎,請大家用辯證的眼光來看待本文內(nèi)容
  • 鳥瞰函數(shù)式編程的概況
  • 簡單了解一下函數(shù)式編程中的基本脈絡(luò)和一些主要思想
  • 看看大家怎么說
  • 本文中的例子均使用javascript或swfit

1.

"函數(shù)式編程" 是一種 "編程范式"(programming paradigm)

也就是如何編寫程序的方法論

In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.

函數(shù)式編程是一種編程模型,他將計(jì)算機(jī)運(yùn)算看做是數(shù)學(xué)中函數(shù)的計(jì)算,并且避免了狀態(tài)以及變量的概念

FP屬于"結(jié)構(gòu)化編程"的一種暖庄,主要思想是把運(yùn)算過程盡量寫成一系列嵌套的函數(shù)調(diào)用;
模塊化是成功編程的關(guān)鍵楼肪,而函數(shù)編程可以極大地改進(jìn)模塊化培廓。

函數(shù)式編程的思想早已遍地開花,例如下面這些巨著:

f00.jpg

f1.jpg

f2.jpg

f3.jpg

f4.jpg

f5.jpg

f6.jpg

f7.jpg

f8.jpg

f9.png

2.

函數(shù)式編程的特性

  • 閉包和高階函數(shù)
  • 惰性計(jì)算
  • 遞歸

閉包

閉包是自包含的函數(shù)代碼塊春叫,可以在代碼中被傳遞和使用肩钠。(自包含:組件不依賴其他組件俘侠,能夠以獨(dú)立的方式供外部使用。)

閉包就是引用了自由變量的函數(shù)蔬将。

這個被引用的自由變量將和這個函數(shù)一同存在爷速,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。

如何來理解這個自由變量呢霞怀?

自由變量是指在函數(shù)中使用的惫东,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量

什么樣的變量是自由變量呢?

如下片斷中的freeVar就是個自由變量:

//javascript
function wrapper() {
  var freeVar = 42;
    function inner() {
    return 2 * freeVar;
}
return inner;

自由變量在閉包生成之前毙石,并不是函數(shù)的一部分廉沮。在函數(shù)被調(diào)用時,閉包才會形成徐矩,函數(shù)將這個自由變量納入自己的作用域滞时,也就是說,自由變量從此與定義它的容器無關(guān)滤灯,以函數(shù)被調(diào)用那一刻為時間點(diǎn)坪稽,成為函數(shù)Context中的成員。

# 例1 
//swift
//定義一個求和閉包
//閉包類型:(Int,Int)->(Int)
var c = 100
let add:(Int,Int)->(Int) = {
    (a,b) in
    return a + b + c
}
//執(zhí)行閉包
let result = add(1,2)

//打印閉包返回值
print("result=\(result)")

----
# 例2
//swift
//包含一個自由變量的閉包
func makeIncrementor(forIncrement amount:Int)->()->Int{
    var runningTotal = 0//這是一個自由變量
    func incrementor()->Int{
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

let incrementByTen = makeIncrementor(forIncrement:10)

incrementByTen()//10
incrementByTen()//20
incrementByTen()//30

高階函數(shù)

在 Wikipedia 中鳞骤,是這么定義高階函數(shù)(higher-order function)的窒百,如果一個函數(shù):

  • 接受一個或多個函數(shù)當(dāng)作參數(shù)
  • 或者,把一個函數(shù)當(dāng)作返回值

那么這個函數(shù)就被稱作高階函數(shù)豫尽。

//swift
import UIKit
var usernames = ["Lves", "Wildcat", "Cc", "Lecoding"]
func backWards(s1: String, s2: String) -> Bool
{
    return s1 > s2
}

var resultName1 = usernames.sorted(by:backWards)

//結(jié)果:resultName1: ["Wildcat", "Lves", "Lecoding", "Cc"]

惰性計(jì)算

惰性計(jì)算(Lazy Evaluation)篙梢,目標(biāo):最小化計(jì)算機(jī)要做的工作。

兩層含義:“延遲計(jì)算”和“短路求值”

在使用延遲求值的時候美旧,表達(dá)式不在它被綁定到變量之后就立即求值渤滞,而是在該值被取用的時候求值。
例如:
x:=expression; (把一個表達(dá)式的結(jié)果賦值給一個變量x)
從字面意義上來看榴嗅,調(diào)用這個表達(dá)式妄呕,將立即把被計(jì)算結(jié)果放置到 x 中,
但是在惰性計(jì)算中录肯,則不會先立即進(jìn)行計(jì)算趴腋,而是直到在后續(xù)表達(dá)式中對 x 的引用有取值的需求時才進(jìn)行計(jì)算。而后續(xù)表達(dá)式自身的求值也可以被延遲论咏,最終為了生成讓外界看到的某個符號而計(jì)算這個快速增長的依賴樹优炬。

來看一個困惑前端的示例, 使用Javascript循環(huán)添加事件:

<meta charset="UTF-8">
<button>第1條記錄</button>
<button>第2條記錄</button>
<button>第3條記錄</button>
<button>第4條記錄</button>
<button>第5條記錄</button>
<button>第6條記錄</button>

<script type="text/javascript">

//test 1 沒有使用閉包
var buttonst_obj = document.getElementsByTagName("button");
for (var i = 0, len = buttonst_obj.length; i < len; i++) {
   buttonst_obj[i].onclick = function() {
      alert(i);
   };
}
 
 上述片斷的結(jié)果是:每個Button彈出的都是6,因?yàn)闆]有形成有效的閉包厅贪。
 
 閉包是有延遲求值特性的蠢护,所以在函數(shù)得到執(zhí)行時,i === 6养涮。(循環(huán)執(zhí)行完成后葵硕,onclick事件才觸發(fā)眉抬,此時i已經(jīng)=6了)
 
 如果我們將它改成下面這樣,讓 i 作為外層函數(shù)的參數(shù)而被內(nèi)層函數(shù)閉包懈凹,結(jié)果則是我們想要的:

//test 2 使用閉包
var buttonst_obj = document.getElementsByTagName("button");
for (var i = 0, len = buttonst_obj.length; i < len; i++) {
     buttonst_obj[i].onclick = clickEvent(i);
}

function clickEvent(i){
   return function () {
      alert(i);
   }
}

</script>
 
 
 Why蜀变? 
 
 因?yàn)檫@個clickEvent(i) 高階函數(shù),
 
 它將 i 作為自由變量傳遞(注意:i 并不是內(nèi)函數(shù)的參數(shù)介评,也不是內(nèi)函數(shù)的一部分)库北,
 
 在 click 時閉包已經(jīng)形成并被傳遞。
 
 

短路求值

作為"&&"和"||"操作符的操作數(shù)表達(dá)式们陆,這些表達(dá)式在進(jìn)行求值時寒瓦,只要最終的結(jié)果已經(jīng)可以確定是真或假,求值過程便告終止坪仇,這稱之為短路求值(short-circuit evaluation)杂腰。這是這兩個操作符的一個重要屬性。


遞歸 (略)


3.

函數(shù)式編程還具有以下五個鮮明的特點(diǎn)

  • 函數(shù)是"第一等公民"
  • 只用"表達(dá)式"椅文,不用"語句"
  • 沒有"副作用"
  • 不修改狀態(tài)
  • 引用透明性

函數(shù)是"第一等公民"

所謂"第一等公民"(first class)喂很,指的是函數(shù)與其他數(shù)據(jù)類型一樣,處于平等地位雾袱,可以賦值給其他變量恤筛,也可以作為參數(shù),傳入另一個函數(shù)芹橡,或者作為別的函數(shù)的返回值。

舉個栗子:下面代碼中的cprint函數(shù)望伦,可以作為另一個函數(shù)的參數(shù)林说。

//swift
func cprint(i:Int){ 
  print(i)
}
let arr = [1, 2, 4]
arr.forEach(cprint)

只用"表達(dá)式",不用"語句"

"表達(dá)式"(expression)是一個單純的運(yùn)算過程屯伞,總是有返回值腿箩;

"語句"(statement)是執(zhí)行某種操作,沒有返回值劣摇。

函數(shù)式編程要求珠移,只使用表達(dá)式,不使用語句末融。

也就是說钧惧,每一步都是單純的運(yùn)算,而且都有返回值勾习。

函數(shù)式編程的動機(jī)浓瞪,一開始就是為了處理運(yùn)算(computation),不考慮系統(tǒng)的讀寫(I/O)巧婶。

"語句"屬于對系統(tǒng)的讀寫操作乾颁,所以就被排斥在外涂乌。

當(dāng)然,實(shí)際應(yīng)用中英岭,不做I/O是不可能的湾盒。

因此,編程過程中诅妹,函數(shù)式編程只要求把I/O限制到最小罚勾,不要有不必要的讀寫行為,保持計(jì)算過程的單純性漾唉。


沒有"副作用"

所謂"副作用"(side effect)荧库,指的是函數(shù)內(nèi)部與外部互動(最典型的情況,就是修改全局變量的值)赵刑,產(chǎn)生運(yùn)算以外的其他結(jié)果分衫。

函數(shù)式編程強(qiáng)調(diào)沒有"副作用",意味著函數(shù)要保持獨(dú)立般此,所有功能就是返回一個新的值蚪战,沒有其他行為,尤其是不得修改外部變量的值铐懊。


不修改狀態(tài)

變量往往用來保存"狀態(tài)"(state)邀桑。

不修改變量,意味著狀態(tài)不能保存在變量中科乎。

函數(shù)式編程使用參數(shù)保存狀態(tài)壁畸,最好的例子就是遞歸。

舉個栗子:計(jì)算階乘 n!

//swift
//使用循環(huán)的方式來計(jì)算階乘茅茂,total變量用于保存臨時狀態(tài)
func cJieChen(n:Int) -> Int {
    var total = 1
    for i in 1...n{
        total = total * i
    }
    return total
}

var t = cJieChen(n:4)

//swift
//使用遞歸方式計(jì)算階乘捏萍,用參數(shù)來傳遞狀態(tài)
func jiechen(n:Int) -> Int {
    if(n==0){
        return 1
    }else{
        return n * jiechen(n:n-1)
    }
}

var a = jiechen(n: 4)

引用透明性

函數(shù)式編程還能夠增強(qiáng)引用透明性,

即如果提供同樣的輸入空闲,那么函數(shù)總是返回同樣的結(jié)果令杈。

也就是說,

表達(dá)式的值不依賴于可以改變的值的全局狀態(tài)碴倾。

這使您可以從形式上推斷程序行為逗噩,

因?yàn)楸磉_(dá)式的意義只取決于其子表達(dá)式而不是計(jì)算順序或者其他表達(dá)式的副作用。

這有助于驗(yàn)證正確性跌榔、簡化算法异雁,甚至有助于找出優(yōu)化它的方法。


副作用

副作用指的是修改系統(tǒng)狀態(tài)的語言結(jié)構(gòu)矫户。

因?yàn)?FP 語言不包含任何賦值語句片迅,變量值一旦被指派就永遠(yuǎn)不會改變。而且皆辽,調(diào)用函數(shù)只會計(jì)算出結(jié)果 ── 不會出現(xiàn)其他效果柑蛇。因此芥挣,F(xiàn)P 語言沒有副作用。


4.

函數(shù)式編程好處

  • 代碼簡潔耻台,開發(fā)快速
  • 接近自然語言空免,易于理解
  • 更方便的代碼管理
  • 易于"并發(fā)編程"
  • 代碼的熱升級

代碼簡潔,開發(fā)快速

函數(shù)式編程大量使用函數(shù)盆耽,減少了代碼的重復(fù)蹋砚,因此程序比較短,開發(fā)速度較快

接近自然語言摄杂,易于理解

函數(shù)式編程的自由度很高坝咐,可以寫出很接近自然語言的代碼。
例如:mansory中的鏈?zhǔn)秸{(diào)用析恢;

更方便的代碼管理

函數(shù)式編程不依賴墨坚、也不會改變外界的狀態(tài),只要給定輸入?yún)?shù)映挂,返回的結(jié)果必定相同泽篮。因此,每一個函數(shù)都可以被看做獨(dú)立單元柑船,很有利于進(jìn)行單元測試(unit testing)和除錯(debugging)帽撑,以及模塊化組合。

易于"并發(fā)編程"

函數(shù)式編程不需要考慮"死鎖"(deadlock)鞍时,因?yàn)樗恍薷淖兞靠骼愿静淮嬖?鎖"線程的問題。不必?fù)?dān)心一個線程的數(shù)據(jù)逆巍,被另一個線程修改专筷,所以可以很放心地把工作分?jǐn)偟蕉鄠€線程,部署"并發(fā)編程"(concurrency)蒸苇。

多核CPU是將來的潮流,所以函數(shù)式編程的這個特性非常重要吮旅。

代碼的熱升級

函數(shù)式編程沒有副作用溪烤,只要保證接口不變,內(nèi)部實(shí)現(xiàn)是外部無關(guān)的庇勃。所以檬嘀,可以在運(yùn)行狀態(tài)下直接升級代碼,不需要重啟责嚷,也不需要停機(jī)鸳兽。Erlang語言早就證明了這一點(diǎn),它是瑞典愛立信公司為了管理電話系統(tǒng)而開發(fā)的罕拂,電話系統(tǒng)的升級當(dāng)然是不能停機(jī)的揍异。


5.

顧慮

函數(shù)式編程常被認(rèn)為嚴(yán)重耗費(fèi)在CPU和存儲器資源

  • 早期的函數(shù)式編程語言實(shí)現(xiàn)時并無考慮過效率問題全陨。
  • 有些非函數(shù)式編程語言為求提升速度,不提供自動邊界檢查或自動垃圾回收等功能衷掷。

6.

柯里化(Currying)

柯里化(Currying)辱姨,又稱部分求值(Partial Evaluation),是一種函數(shù)式編程思想戚嗅,就是把接受多個參數(shù)的函數(shù)轉(zhuǎn)換成接收一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù)雨涛,并且返回一個接受余下參數(shù)的新函數(shù)技術(shù)。

currying.jpg

Currying wiki

例3

在swift中利用閉包進(jìn)行柯里化

//swift

//假設(shè)原先有一個下面這樣的函數(shù)
func addOrignal(_ a1:Int,_ a2:Int) -> Int {
    return a1+a2
}
let a1 = addOrignal(2,3)
print("a1=\(a1)")


//使用柯里化的方式進(jìn)行改造
/**
 這個栗子中的4行代碼懦胞,講述的大概是下面的故事:
 (1)函數(shù)addTo的返回值為 (Int) -> Int :記作R替久,R仍然是一個函數(shù);
 (2)函數(shù)addTo接收一個數(shù)字類型的參數(shù)(adder)躏尉;
 (3)返回函數(shù)(R)接收一個數(shù)字類型的參數(shù)(num)蚯根;
 (4)返回函數(shù)(R)的返回值是一個數(shù)值,這個數(shù)值為:num+adder
 (5)adder是函數(shù)addTo的參數(shù)醇份,而num是函數(shù)R的參數(shù)稼锅,當(dāng)函數(shù)R被創(chuàng)建后,參數(shù)adder被閉包到函數(shù)R當(dāng)中僚纷,并參與函數(shù)R的運(yùn)算矩距;
*/
func addTo(_ adder:Int) -> (Int) -> Int {
    return {
        num in
        return num + adder
    }
}



let add2 = addTo(2)
let a2 = add2(3)
print("a2=\(a2)");
//a2=4

let add3 = addTo(3)
let a3 = add3(3)
print("a3=\(a3)");
//a3=6


Map & Reduce

MapReduce是一種編程模型,用于大規(guī)模數(shù)據(jù)集(大于1TB)的并行運(yùn)算怖竭。概念"Map(映射)"和"Reduce(歸約)"锥债,是它們的主要思想,都是從函數(shù)式編程語言里借來的痊臭,還有從矢量編程語言里借來的特性哮肚。它極大地方便了編程人員在不會分布式并行編程的情況下,將自己的程序運(yùn)行在分布式系統(tǒng)上广匙。 當(dāng)前的軟件實(shí)現(xiàn)是指定一個Map(映射)函數(shù)允趟,用來把一組鍵值對映射成一組新的鍵值對,指定并發(fā)的Reduce(歸約)函數(shù)鸦致,用來保證所有映射的鍵值對中的每一個共享相同的鍵組潮剪。

pipeline

這個技術(shù)的意思是,把函數(shù)實(shí)例成一個一個的action分唾,然后抗碰,把一組action放到一個數(shù)組或是列表中,然后把數(shù)據(jù)傳給這個action list绽乔,數(shù)據(jù)就像一個pipeline一樣順序地被各個函數(shù)所操作弧蝇,最終得到我們想要的結(jié)果。


7.

Lambda表達(dá)式

“Lambda 表達(dá)式”(lambda expression)是一個匿名函數(shù),Lambda表達(dá)式基于數(shù)學(xué)中的λ演算得名看疗,直接對應(yīng)于其中的lambda抽象(lambda abstraction)沙峻,是一個匿名函數(shù),即沒有函數(shù)名的函數(shù)鹃觉。Lambda表達(dá)式可以表示閉包(注意和數(shù)學(xué)傳統(tǒng)意義上的不同)专酗。

//swift
var resultName2 = usernames.sorted() {
    (s1: String, s2: String) -> Bool in
    return s1 < s2
}
sorted方法使用匿名閉包函數(shù)

8.

反面的觀點(diǎn)

不論是面向?qū)ο缶幊踢€是函數(shù)式編程,如果你走了極端盗扇,那都是錯誤的祷肯。
面向?qū)ο缶幊痰臉O端是一切都是對象(純面向?qū)ο?。
函數(shù)式編程的極端是純函數(shù)式編程語言疗隶。

為什么說面向?qū)ο缶幊毯秃瘮?shù)式編程都有問題(30)

反人類
難找工作
好多語言設(shè)計(jì)得不real world佑笋,用來解決實(shí)際問題很無(sha)力(bi)
學(xué)好函數(shù)式編程是要數(shù)學(xué)基礎(chǔ)的,尤其是了解數(shù)學(xué)是如何從更高層面抽象已經(jīng)被抽象過的東西斑鼻。
學(xué)的人容易感到智商不夠用
union-find算法的Dr. Harrop說:目前我們還沒有發(fā)現(xiàn)一個有效率的純函數(shù)的union-find集合蒋纬。
也就是說:對于有狀態(tài)的操作命令式操作會比聲明式操作更有效率。

純函數(shù)式編程的缺點(diǎn)

april.jpg
w.jpg

參考資料

(1)Functional programming

(2)百度百科

(3)閉包1 閉包2

(4)Lambda calculus

(5)Currying

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坚弱,一起剝皮案震驚了整個濱河市蜀备,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荒叶,老刑警劉巖碾阁,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異些楣,居然都是意外死亡脂凶,警方通過查閱死者的電腦和手機(jī)愁茁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹅很,“玉大人,你說我怎么就攤上這事促煮∈程” “怎么了污茵?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵葬项,是天一觀的道長泞当。 經(jīng)常有香客問我,道長民珍,這世上最難降的妖魔是什么襟士? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任陋桂,我火速辦了婚禮,結(jié)果婚禮上宣渗,老公的妹妹穿的比我還像新娘梨州。我一直安慰自己,他們只是感情好暴匠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布每窖。 她就那樣靜靜地躺著,像睡著了一般蟆炊。 火紅的嫁衣襯著肌膚如雪崇败。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天缩膝,我揣著相機(jī)與錄音岸霹,去河邊找鬼。 笑死痛黎,一個胖子當(dāng)著我的面吹牛刮吧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播井厌,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼器赞!你這毒婦竟也來了墓拜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤夏醉,失蹤者是張志新(化名)和其女友劉穎授舟,沒想到半個月后贸辈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奢啥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年桩盲,在試婚紗的時候發(fā)現(xiàn)自己被綠了席吴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡柬姚,死狀恐怖庄涡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撕捍,我是刑警寧澤泣洞,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站阀蒂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜义钉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一捶闸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贪绘,春花似錦央碟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至攻走,卻和暖如春此再,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背引润。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工淳附, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人别凹。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓洽糟,卻偏偏與公主長得像堕战,于是被迫代替她去往敵國和親嘱丢。 傳聞我的和親對象是個殘疾皇子祠饺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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