本系列文章為個(gè)人學(xué)習(xí)筆記:禁止轉(zhuǎn)載
對(duì)于一個(gè)函數(shù)來說,把它的參數(shù)和返回值放在一起,就形成了它的簽名,這是用于描述函數(shù)自身屬性的一種類型。
參數(shù)名稱不再是函數(shù)類型的一部分
不過沒關(guān)系,我們從一個(gè)簡(jiǎn)單的例子開始。對(duì)于上一節(jié)中,我們實(shí)現(xiàn)的mul:
func mul(m: Int, of n: Int) -> Int {
return m * n
}
如果我們用type inference定義一個(gè)和mul類型相同的函數(shù)變量:
let fnMul = mul
之后,我們?cè)撊绾问褂?code>fnMul呢?在Swift 2里,是這樣的:
// ONLY 6 in Swift 2
// WRONG in Swift 3
fnMul(m: 2, n: 3)
而到了Swift 3里,編譯器會(huì)報(bào)錯(cuò)。
所以我們只能這樣使用fnMul:
fnMul(2, 3) // 6
這說明什么呢?從結(jié)果上來看m:n:已經(jīng)在不再是mul簽名的一部分了。在Swift 3里,mul和fnMul的類型,實(shí)際上都是:(Int, Int) -> Int。
然而,為什么要做這樣的改動(dòng)呢?我們繼續(xù)來看一個(gè)在Swift 2中因?yàn)楹瘮?shù)簽名帶來的詭異的問題。
假設(shè),我們還有一個(gè)用于計(jì)算整數(shù)除法的函數(shù):
func div(a: Int, b: Int) -> Int {
return a / b
}
然后,對(duì)于下面這段代碼,你期望最后fnDiv得到什么結(jié)果呢?
// !!! Swift 2 ONLY !!!
var fnDiv: (a: Int, b: Int) -> Int = div
fnDiv = mul
fnDiv(a: 2, b: 3) // This will call mul(m: 2, n: 3)
// !!! Swift 2 ONLY !!!
結(jié)果會(huì)是6,fnDiv(a: 2, b: 3)實(shí)際上調(diào)用了mul。造成這個(gè)結(jié)果的原因有2:
- 首先,
mul和div都接受2個(gè)Int作為參數(shù),并返回一個(gè)Int,因此,盡管它們的參數(shù)名不同,但它們的類型是兼容的,我們可以讓fnDiv = mul; - 其次,由于參數(shù)名又是函數(shù)簽名的一部分,我們使用
fnDiv的時(shí)候,必須帶上a:b:;
因此,就造成了這種完全是div的用法,而暗地里執(zhí)行了mul的錯(cuò)覺。如果這段代碼隱藏在你的源文件中,你能輕易發(fā)現(xiàn)問題的原因么?
正是由于這種原因,在Swift 3里,參數(shù)名不再是函數(shù)簽名的一部分了,mul和div的類型都是(Int, Int) -> Int。這樣:
- 之前我們定義的
fnMul,只能用fnMul(2, 3)這樣的形式調(diào)用; - 無論
fnMul被賦值成任何函數(shù),調(diào)用方式都是統(tǒng)一的:fnMul(num1, num2),這樣就不會(huì)有因?yàn)閰?shù)名帶來的調(diào)用錯(cuò)覺了;
為什么管函數(shù)類型叫做“一等公民”?
理解了函數(shù)類型之后,接下來我們來聊另外一個(gè)話題。你也許經(jīng)常會(huì)聽到類似“函數(shù)在Swift中是一等公民”這樣的說法。然而,對(duì)一門編程語言來說,“一等公民”意味著什么呢?
簡(jiǎn)單來說,就是函數(shù)這種類型,和Swift其它類型有著完全相同的語法功能。它們主要包括:
- 可以用來定義變量,例如我們之前定義的
fnMul和fnDiv,這種類型的變量同樣可以當(dāng)作函數(shù)來調(diào)用; - 可以當(dāng)成函數(shù)參數(shù);
- 可以被函數(shù)返回;
而對(duì)于后兩種情況之所以是很重要的語言特性,是因?yàn)樗鼈儤?gòu)成了函數(shù)式編程的基礎(chǔ)。例如,我們定義一個(gè)表達(dá)計(jì)算概念的函數(shù):
func calc<T>(_ first: T,
_ second: T,
_ fn: (T, T) -> T) -> T {
return fn(first, second)
}
在這個(gè)函數(shù)的定義里,calc<T>就接受一個(gè)函數(shù)類型的參數(shù),然后,我們可以像傳遞數(shù)字一樣,來傳遞函數(shù)變量:
calc(2, 3, mul) // 6
calc(2, 3, div) // 0
類似的,我們也可以把函數(shù)類型當(dāng)成返回值,例如下面這個(gè)有點(diǎn)兒特殊的mul方法:
func mul(_ a: Int) -> (Int) -> Int {
func innerMul(_ b: Int) -> Int {
return a * b
}
return innerMul
}
它只接受一個(gè)參數(shù),并返回一個(gè)函數(shù),在返回的函數(shù)里,接受另外一個(gè)參數(shù),并最終完成相乘的運(yùn)算。因此,我們可以用兩種不同的方式來調(diào)用它:
let mul2By = mul(2)
mul2By(3) // 6
mul(2)(3) // 6
第一種方式,我們先用一個(gè)變量保存mul返回的函數(shù),然后通過再傳遞一個(gè)單參數(shù)來最終完成運(yùn)算?;蛘?,我們可以直接通過()把兩次函數(shù)調(diào)用串聯(lián)起來。因此,盡管currying function在Swift 3中被廢除了,但我們還是可以通過這樣的方法來實(shí)現(xiàn)同樣的效果。