1详幽、假如我們需要寫一個函數(shù),它接受一個給定的整型數(shù)組,通過計算得到并返回一個新數(shù)組唇聘,新數(shù)組各項為原數(shù)組中對應(yīng)的整型數(shù)據(jù)加一版姑。這個簡單的例子僅僅需要使用一個for
循環(huán)就能解決,實現(xiàn)如下:
private func incrementArray(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(x + 1)
}
return result
}
2迟郎、現(xiàn)在假設(shè)我們還需要一個函數(shù)剥险,用于生成一一個每項都為參數(shù)數(shù)組對應(yīng)項兩倍的新數(shù)組。這同樣能很容易使用一個for
循環(huán)實現(xiàn):
private func doubleArray(xs: [Int]) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(x * 2)
}
return result
}
3宪肖、這兩個函數(shù)有大量相同的代碼表制,我們能不能將沒有區(qū)別的地方抽象出來,并單獨寫一個體現(xiàn)這種模式且更通用的函數(shù)呢控乾?像這樣的函數(shù)需要追加一個新參數(shù)來接受一個函數(shù)么介,這個參數(shù)能根據(jù)各個數(shù)組項計算得到新的整型數(shù)值:
private func computeIntArray(xs: [Int], transform: (Int) -> Int) -> [Int] {
var result: [Int] = []
for x in xs {
result.append(transform(x))
}
return result
}
4、現(xiàn)在蜕衡,取決于我們想如何根據(jù)原數(shù)組得到一個新數(shù)組壤短,我們可以向函數(shù)傳遞不同的參數(shù)來實現(xiàn),如下
let array = [1, 2, 3, 4, 5]
print(computeIntArray(xs: array, transform: { x in
x * 2
}))
5慨仿、代碼仍然不像想象中的那么靈活久脯,假如我們想要得到一個布爾類型的新數(shù)組,用于表示原數(shù)組中對應(yīng)的數(shù)字是否是偶數(shù)镰吆,我們可以嘗試編寫一些像下面這樣的代碼:
private func isEvenArray(xs: [Int]) -> [Bool] {
computeIntArray(xs: xs) { x in
x % 2 == 0
}
}
不幸的是帘撰,這段代碼導(dǎo)致了一個類型錯誤。問題在于我們的 computeIntArray
函數(shù)接受一個 (Int) -> Int
類型的參數(shù)万皿,也就是說該參數(shù)是一個返回整型值的參數(shù)摧找。而在 isEvenArray
函數(shù)的定義中,我們傳遞了一個 (Int) -> Bool
類型的參數(shù)相寇,于是導(dǎo)致了類型錯誤慰于。
6钮科、我們該如何解決這個問題呢唤衫?一種最普通的方案是定義新版本的 computeBoolArray
函數(shù),接受一個 (Int) -> Bool
類型的參數(shù)绵脯,在這里的實現(xiàn)我就不寫了佳励,但是這個方案的擴展性并不好。如果接下來我們需要計算 String
類型呢蛆挫?是否還需要定義一個高階函數(shù)來接受 (Int) -> String
類型的參數(shù)赃承?
幸運的是,該問題有一個解決方案:我們可以使用 泛型 悴侵。computeIntArray
和 computeBoolArray
的定義是相同的瞧剖,唯一的區(qū)別在于類型簽名 (type signature)
。假如我們定義一個相似的函數(shù) computeStringArray
來支持 String
類型,其函數(shù)體將會與先前兩個函數(shù)完全一致抓于。事實上做粤,相同部分的代碼可以用于 任何 類型。我們真正想做的是寫一個能夠適用于每種可能類型的泛型函數(shù)
private func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
var result: [T] = []
for x in xs {
result.append(transform(x))
}
return result
}
關(guān)于這段代碼捉撮,最有意思的是他的類型簽名怕品。理解這個類型簽名有助于你將 genericComputeArray<T>
理解為一個函數(shù)族。類型參數(shù) T
的每個選擇都會確定一個新函數(shù)巾遭。該函數(shù)接受一個整型數(shù)組和一個 Int -> T
類型的函數(shù)作為參數(shù)肉康,并返回一個 [T]
類型的數(shù)組。
7灼舍、我們?nèi)阅苓M一步將這個函數(shù)一般化吼和。沒有理由讓它僅能對類型為 [Int]
的輸入數(shù)組進行處理。將數(shù)組類型進行抽象片仿,能得到下面這樣的類型簽名:
private func map<Element, T>(xs:[Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for x in xs {
result.append(transform(x))
}
return result
}
這里我們寫了一個 map
函數(shù)纹安,它在兩個維度都是通用的:對于任何 Element
的數(shù)組和 transform: (Element) -> T
函數(shù),它都會生成一個 T
的新數(shù)組砂豌。這個 map
函數(shù)甚至比我們之前看到的 genericComputeArray
函數(shù)更通用厢岂。事實上,我們可以通過 map
來定義 genericComputeArray:
private func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
return map(xs: xs, transform: transform)
}
同樣的阳距,上述函數(shù)的定義并沒有什么太過特別之處:函數(shù)接受 xs
和 transform
兩個參數(shù)之后塔粒,將它們傳遞給 map
函數(shù),然后返回結(jié)果筐摘。關(guān)于這個定義卒茬,最有意思非類型莫屬。 genericComputeArray(_: transform:)
是 map
函數(shù)的一個實例咖熟,只是它有一個更具體的類型圃酵。實際上,比起定義一個頂層 map
函數(shù)馍管,按照 Swift
的慣例將 map
定義為 Array
的擴展會更合適:
extension Array {
func map<T>(transform: (Element) -> T) -> [T] {
var result: [T] = []
for x in self {
result.append(transform(x))
}
return result
}
}
我們在函數(shù)的 transform
參數(shù)中所使用的 Element
類型源自于 Swift
的 Array
中對 Element
所進行的泛型定義郭赐。
作為 map(xs, transform)
的替代,我們現(xiàn)在可以通過 xs.map(transform)
來調(diào)用 Array
的 map
函數(shù):
private func genericComputeArray<T>(xs: [Int], transform: (Int) -> T) -> [T] {
return xs.map(transform)
}
8确沸、想必你會很樂意聽到其實并不需要自己像這樣來定義 map
函數(shù)捌锭,因為它已經(jīng)是 Swift
標準庫的一部分了(實際上,它基于 SequenceType
協(xié)議被定義)罗捎。本文章的重點并不是說你應(yīng)該自己定義 map
;我們只是想要告訴你 map
的定義中并沒有什么復(fù)雜難懂的魔法--你能夠輕松地自己定義它观谦!。