代碼地址
泛型介紹
需求為寫一個(gè)這樣的函數(shù)惭载,此函數(shù)接收一個(gè)參數(shù)為整型數(shù)組,返回一個(gè)一個(gè)新數(shù)組响巢,新數(shù)組各項(xiàng)為原數(shù)組對應(yīng)的數(shù)據(jù)加一描滔。
func incrementArray(array: [Int]) -> [Int] {
var result: Array<Int> = []
for i in array {
result.append(i + 1)
}
return result
}
新增需求,再寫一個(gè)函數(shù)踪古,此函數(shù)接收一個(gè)參數(shù)為整型數(shù)組含长,返回一個(gè)一個(gè)新數(shù)組券腔,新數(shù)組各項(xiàng)為原數(shù)組對應(yīng)的數(shù)據(jù)兩倍。
func doubleArray(array: [Int]) -> [Int] {
var result: [Int] = []
for i in array {
result.append(i*2)
}
return result;
}
至此拘泞,發(fā)現(xiàn)這兩個(gè)函數(shù)有大量的代碼相同得封,能不能寫一個(gè)更通用的函數(shù)髓霞?新增一個(gè)參數(shù)接收一個(gè)函數(shù),這個(gè)參數(shù)根據(jù)各個(gè)數(shù)組項(xiàng)計(jì)算新的值。
func computeIntArray(array: [Int], transform: (Int) -> Int) -> [Int] {
var result: [Int] = []
for i in array {
result.append(transform(i))
}
return result
}
這樣就可以簡化一點(diǎn)incrementArray项秉、doubleArray這兩個(gè)函數(shù):
func incrementArray1(array: [Int]) -> [Int] {
return computeIntArray(array: array, transform: { x in x + 1 })
}
func doubleArray1(array: [Int]) -> [Int] {
return computeIntArray(array: array, transform: { x in x*2 })
}
代碼任然不夠靈活溃卡,如果需要得到一個(gè)布爾型數(shù)組沮焕,用于表示對應(yīng)的數(shù)字是否為偶數(shù)对室。
func isEvenArray(array: [Int] -> [Bool]) {
return computeIntArray(array: array, transform: { x in x%2 == 0 })
}
不幸的是上面這段代碼無法使用computeIntArray函數(shù),因?yàn)轭愋湾e(cuò)誤强岸。
于是可以定義一個(gè)新的函數(shù)接受一個(gè)Int -> Bool類型的函數(shù)作為參數(shù)锻弓。
但是這個(gè)方案并不好,如果還要計(jì)算String類型蝌箍,還得定義一個(gè)高階函數(shù)來接受一個(gè)Int -> String類型的函數(shù)作為參數(shù)青灼。
幸運(yùn)的是泛型可以解決這個(gè)問題。相同的代碼可以適用任何類型妓盲,寫一個(gè)適用于每種可能類型的泛型函數(shù):
func genericComputeArray<T>(array: [Int], transform: (Int) -> T) -> [T] {
var result: [T] = []
for i in array {
result.append(transform(i))
}
return result
}
可以進(jìn)一步一般化這個(gè)函數(shù)杂拨,沒有理由僅能對[Int]型的輸入數(shù)組進(jìn)行處理,可以將數(shù)組類型也進(jìn)行抽象:
func map<Element, T>(array: [Element], transform: (Element) -> T) -> [T] {
var result: [T] = []
for i in array {
result.append(transform(i))
}
return result
}
對于這個(gè)map函數(shù)在兩個(gè)維度是通用的本橙,任何類型的數(shù)組和transform函數(shù)扳躬。
按照Swift的慣例將map函數(shù)定義為Array的擴(kuò)展會比定義為頂層函數(shù)更合適:
extension Array {
func map<T>(transform: (Element) -> T) -> [T] {
var result: [T] = []
for i in self {
result.append(transform(i))
}
return result
}
}
Element源于Swift的Array中對Element所進(jìn)行的泛型定義脆诉。
map函數(shù)已經(jīng)是Swift標(biāo)準(zhǔn)庫中的一部分(基于SequenceType協(xié)議被定義)
頂層函數(shù)和擴(kuò)展
在一開始將創(chuàng)建map函數(shù)時(shí)甚亭,為了簡便起見,選擇了頂層函數(shù)的版本击胜。不過最終將map的泛型版本定義為Array的擴(kuò)展亏狰,這和Swift標(biāo)準(zhǔn)庫的實(shí)現(xiàn)十分相似。
隨著協(xié)議擴(kuò)展(protocol extensions)偶摔,開發(fā)者有了強(qiáng)有力的工具來定義擴(kuò)展--不僅可以在Array這樣的具體類型上上進(jìn)行定義暇唾,還可以在SequenceType這樣的協(xié)議上定義擴(kuò)展。
把處理確定類型的函數(shù)辰斋,定義為該類型的擴(kuò)展策州。這樣的優(yōu)點(diǎn)是:
- 自動(dòng)補(bǔ)全更完善
- 命名更少
- 代碼結(jié)構(gòu)更清晰
Filter
filter函數(shù)像之前定義的map函數(shù)一樣,接收一個(gè)函數(shù)作為參數(shù)宫仗,這個(gè)參數(shù)類型是(Elemeng) -> Bool--對于數(shù)組中的所有元素够挂,此函數(shù)判定它是否被包含在結(jié)果中:
extension Array {
func filter(includeElement: (Element) -> BooleanLiteralType) -> [Element] {
var result: [Element] = []
for i in self {
if (includeElement(i)) { result.append(i) }
}
return result
}
}
Swift標(biāo)準(zhǔn)庫中的數(shù)組類型已經(jīng)定義好了filter函數(shù)。
有沒有更通用的函數(shù)藕夫,可以用來定義map孽糖,也可以定義filter枯冈?
Reduce
reduce函數(shù)將變量初始化為某個(gè)值,然后對數(shù)組的每一項(xiàng)進(jìn)行遍歷办悟,以某種方式更新結(jié)果尘奏。
extension Array {
func reduce<T>(initial: T, combine: (T, Element) -> T) -> T {
var result: T = initial
for i in self {
result = combine(result, i)
}
return result
}
}
reduce函數(shù)的泛型體現(xiàn)在兩個(gè)方面:
- 對于任意類型的數(shù)組,它會計(jì)算一個(gè)T類型的返回值病蛉。
- 需要一個(gè)T類型的初始值炫加,以及一個(gè)用于更新for循環(huán)中變量值的函數(shù)combine: (T, Elemeng) -> T。
使用reduce定義函數(shù)铺然。除了使用閉包琢感,也可以使用操作符作為最后一個(gè)參數(shù),使代碼更簡短探熔。
func sumUsingReduce(xs: [Int]) -> Int {
return xs.reduce(initial: 0, combine: { result, i in result + i })
}
func productUsingReduce(xs: [Int]) -> Int {
return xs.reduce(initial: 1, combine: *)
}
func concatUsingReduce(xs: [String]) -> String {
return xs.reduce(initial: "", combine: +)
}
甚至可以使用reduce重新定義map驹针、filter。
extension Array {
func mapUsingReduce<T>(transform: (Element) -> T) -> [T] {
return self.reduce(initial: [T](), combine: { result, i in result + [transform(i)] })
}
func filterUsingReduce(includeElement: (Element) -> Bool) -> [Element] {
return self.reduce(initial: [Element](), combine: { result, i in includeElement(i) ? result + [i] : result })
}
}
能夠用reduce表示這些函數(shù)诀艰,說明了reduce能夠通過通用的方法來體現(xiàn)一種常見的編程模式:遍歷數(shù)組并計(jì)算結(jié)果柬甥。
注意:
使用reduce來定義一切非常的簡便,但是實(shí)踐中這往往不是一個(gè)好主意其垄。原因是苛蒲,代碼在最終的運(yùn)行期間大量復(fù)制生成的數(shù)組,換句話說绿满,它不得不反復(fù)的分配內(nèi)存釋放內(nèi)存以及復(fù)制內(nèi)存中的內(nèi)容臂外。像之前那樣用一個(gè)可變數(shù)組定義map顯然效率更高。理論上喇颁,編譯器可以優(yōu)化代碼使其速度和可變數(shù)組一樣快漏健,但是Swift2.0并沒有做優(yōu)化。
實(shí)際運(yùn)用
假設(shè)有一個(gè)City結(jié)構(gòu)體橘霎,由城市名稱和人口(萬)組成蔫浆。并定義了一些城市示例。
struct City {
let name: String
let population: Int
}
let beijing = City(name: "北京", population: 4000)
let shanghai = City(name: "上海", population: 3500)
let guangzhou = City(name: "廣州", population: 3000)
let shenzhen = City(name: "深圳", population: 2500)
let citys = [beijing, shanghai, guangzhou, shenzhen]
現(xiàn)在賽選出居民數(shù)量至少為3000萬的城市姐叁,并打印一份這些城市名稱及人口數(shù)的列表瓦盛。
extension City {
func cityByScalingPopulation() -> City { return City(name: self.name, population: self.population*10000) }
}
let table = citys.filter(includeElement: { city in city.population >= 3000 }).map(transform: { city in city.cityByScalingPopulation() }).reduce(initial: "\n城市:人口\n", combine: { result, city in result + "\n\(city.name):\(city.population)\n"})
泛型和Any類型
Any類型和泛型都能定義接收不同類型的參數(shù)的函數(shù)。然而兩者之間的重要區(qū)別是:
- 泛型可以用于定義靈活的函數(shù)外潜,類型檢查仍由編譯器負(fù)責(zé)原环。
- Any類型直接避開了Swift的類型系統(tǒng)(盡可能避免使用)。
用泛型和Any類型分別構(gòu)造一個(gè)函數(shù)处窥,除了返回它的參數(shù)嘱吗,其他什么也不做。
func noOp<T>(x: T) -> T { return x }
func noOpAny(x: Any) -> Any { return x }
noOp和noOpAny函數(shù)都接收任意參數(shù)碧库,關(guān)鍵區(qū)別在于返回值柜与,noOp的返回值類型必須跟參數(shù)一樣巧勤,而noOpAny的返回值可以為任何類型,甚至可以和參數(shù)的類型不同弄匕。如下函數(shù)noOpWrong會導(dǎo)致類型錯(cuò)誤:
func noOpWrong<T>(x: T) -> T { return 0 }
func noOpAnyWrong(x: Any) -> Any { return 0 }
泛型函數(shù)的類型十分豐富颅悉,考慮把上一篇Swift函數(shù)式編程二(封裝Core Image)中的函數(shù)組合運(yùn)算符>>>定義為泛型版本:
precedencegroup ComposeFunctionPrecedence {
associativity: left
}
infix operator >>>: ComposeFunctionPrecedence
func >>><A, B, C>(f: @escaping (A) -> B, g: @escaping (B) -> C) -> (A) -> C {
return { x in g(f(x)) }
}
最后用相同的方式定義一個(gè)泛型函數(shù),這個(gè)函數(shù)的作用是將接受兩個(gè)參數(shù)作為輸入的函數(shù)進(jìn)行柯里化處理迁匠,生成相應(yīng)的柯里化版本:
func curry<A, B, C>(f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { x in return { y in f(x, y) } }
}
使用泛型剩瓶,能夠在不犧牲類型安全的情況下寫出靈活的函數(shù);而使用Any類型城丧,則無法辦到延曙。