Swift支持函數(shù)式編程,這一篇介紹Swift中的函數(shù)。
高階函數(shù),指可以將其他函數(shù)作為參數(shù)或者返回結(jié)果的函數(shù)。
Swift中的函數(shù)都是高階函數(shù),這和Scala,Haskell一致。與此對照的是,Java中沒有高階函數(shù)(Java 7支持閉包之前)。Java中方法沒法單獨(dú)存在,方法總是需要和類捆綁在一起。當(dāng)你需要將一個(gè)函數(shù)傳遞作為參數(shù)給另外一個(gè)函數(shù)時(shí),需要一個(gè)類作為載體來攜帶函數(shù)。這也是Java中監(jiān)聽器(Listener)的做法。
高階函數(shù)對于函數(shù)式語言很重要,原因至少有兩個(gè):
首先,高階函數(shù)意味著您可以使用更高的抽象,因?yàn)樗试S我們引入計(jì)算的通用方法。例如,可通過抽象出一個(gè)通用機(jī)制,遍歷數(shù)組并向其中的每個(gè)元素應(yīng)用一個(gè)(或多個(gè))高階函數(shù)。高階函數(shù)可以被組合成為更多更復(fù)雜的高階函數(shù),來創(chuàng)造更深層的抽象。
一等函數(shù),進(jìn)一步擴(kuò)展了函數(shù)的使用范圍,使得函數(shù)成為語言中的“頭等公民”。這意味函數(shù)可在任何其他語言結(jié)構(gòu)(比如變量)出現(xiàn)的地方出現(xiàn)。一等函數(shù)是更嚴(yán)格的高階函數(shù)。Swift中的函數(shù)都是一等函數(shù)。
閉包是一個(gè)會(huì)對它內(nèi)部引用的所有變量進(jìn)行隱式綁定的函數(shù)。也可以說,閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。
?函數(shù)實(shí)際上是一種特殊的閉包,你可以使用{}來創(chuàng)建一個(gè)匿名閉包。使用 in 來分割參數(shù)和返回類型。
let r = 1...3
let t = r.map { (i: Int) -> Int in
return i * 2
}
map函數(shù)遍歷了數(shù)組,用閉包處理了所有元素。并返回了一個(gè)處理過的新數(shù)組。
Objective-C在后期加入了對閉包支持。閉包是一種一等函數(shù)。通過支持閉包,Objective-C拓展其語言表達(dá)能力。但是如果將Swift的閉包語法與Objective-C的閉包相比,Swift的閉包顯得相當(dāng)簡潔和優(yōu)雅,Objective-C的閉包則顯得有些繁重復(fù)雜。
函數(shù)柯里化(Function Curring),是指接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),該函數(shù)返回一個(gè)接受余下參數(shù)的新函數(shù)。這個(gè)名詞來源于邏輯學(xué)家 Haskell Curring。編程語言Haskell也取自這位邏輯學(xué)家的名字。
Haskell中函數(shù)都可以柯里化。在Haskell里的函數(shù)參數(shù)的型別聲明也暗示了函數(shù)是柯里化的。Haskell中,返回值和參數(shù)之間,各個(gè)參數(shù)之間都是以->
分隔。這是因?yàn)?,如果你向可以接受多個(gè)參數(shù)的函數(shù)傳入一個(gè)參數(shù),函數(shù)仍然有返回值。它的返回值是另外一個(gè)函數(shù)。這個(gè)函數(shù)可以接受剩余的參數(shù),我們稱這個(gè)返回的函數(shù)為不全呼叫函數(shù)
。本質(zhì)上講,Haskell的所有函數(shù)都只有一個(gè)參數(shù)。
下面語句在命令行中展示了Haskell里max的型別:
Prelude> :type max
max :: Ord a => a -> a -> a
其實(shí)也可以寫作:
max :: (Ord a) => a -> (a -> a)
這意味著,如果向max傳入一個(gè)參數(shù)a,將返回一個(gè)型別為(a -> a)
的函數(shù)。
柯里化為構(gòu)造新函數(shù)帶來了方便。也免除了一些一次性的中間函數(shù)的編寫工作。
Swift可以寫出柯里化函數(shù),雖然它還是保留了和Java類似的非柯里化函數(shù)的寫法。以max函數(shù)為例,Swift中柯里化函數(shù)如下:
func max(a: Int)(b: Int) -> Int {
return a > b ? a : b;
}
let max3 = max(3)
max3(b: 5)
一個(gè)簡單的例子,找出1到10這個(gè)數(shù)組里的奇數(shù)。使用Java語言的思維(循環(huán)控制其實(shí)是過程式語言的思維),通常的寫法會(huì)是這樣:
var odds = [Int]()
for i in 1...10 {
if i % 2 == 1 {
odds.append(i)
}
}
println(odds)
輸出結(jié)果為:[1, 3, 5, 7, 9]。而函數(shù)式的寫法更為簡單:
odds = Array(1...10).filter { $0 % 2 == 1 }
println(odds)
函數(shù)式的寫法更為簡單的原因是,放棄了對循環(huán)的控制,而使用函數(shù)處理序列。如何處理序列,即循環(huán)體里應(yīng)該寫的代碼,在函數(shù)式編程中是由一個(gè)函數(shù)(通常會(huì)是閉包)傳入。在計(jì)算機(jī)的底層對語言的實(shí)現(xiàn)中,仍然使用了循環(huán)控制這樣的概念。但是,在編寫函數(shù)式編程語言時(shí),你并不需要這個(gè)概念。
另外一個(gè)簡單的例子,如何找出1到10這個(gè)數(shù)組里的奇數(shù),并且求它們的和呢?通常的寫法會(huì)是這樣:
var sumOdds = 0
var odds = [Int]()
for i in 1...10 {
if i % 2 == 1 {
odds.append(i)
sumOdds += i
}
}
println(sumOdds)
而函數(shù)式版本會(huì)是這樣:
let sum = Array(1...10)
.myFilter { (i) in i % 2 == 1}
.reduce(0) { (total, number) in total + number }
println(sum)
如果序列中的某些值做操作,過程式語言中,由于存在循環(huán)變量,就可以對循環(huán)所處的位置進(jìn)行判斷。而函數(shù)式編程語言的做法是使用函數(shù)構(gòu)建一個(gè)符合條件的新序列,這里是Array(1...10).myFilter { (i) in i % 2 == 1}
,用于代表1到10里的奇數(shù)。然后再對新序列做進(jìn)一步操作。這個(gè)例子中,使用reduce
函數(shù)對新序列求和。
Haskell這種純函數(shù)式編程語言,由于不需要,是沒有循環(huán)控制語句的,你看不到for,while這樣的關(guān)鍵字。但在Swift中,程序員在使用更高層級的抽象的同時(shí)意味著需要放棄對細(xì)節(jié)的控制。但是,這并不意味著無法在需要的時(shí)候回收控制。以函數(shù)式思維的一個(gè)重要方面是知道放棄多少控制,以及何時(shí)放棄。
函數(shù)式編程思想中,面對復(fù)雜問題時(shí),會(huì)使用一個(gè)個(gè)函數(shù)組合來為復(fù)雜問題建模。我們使用一個(gè)判斷質(zhì)數(shù)的例子來表現(xiàn)函數(shù)式編程的這一特點(diǎn)。我們會(huì)分別使用面向?qū)ο缶幊毯秃瘮?shù)式編程實(shí)現(xiàn)判斷質(zhì)數(shù)的算法,以對比兩者的不同。
質(zhì)數(shù)是因數(shù)只能是及其本身的整數(shù)。我們將使用這種算法:首先找出數(shù)字的因數(shù),然后求所有因數(shù)的和,如果所有因數(shù)和為該數(shù)字加一,就可以確定該數(shù)字是質(zhì)數(shù)。
為了先用面向?qū)ο蟮耐ǔ懛▉韺?shí)現(xiàn)該算法:
class PrimeNumberClassifier {
let number: Int
init(number: Int){
self.number = number
}
func isFactor(potential: Int) -> Bool {
return number % potential == 0
}
func getFactors() -> [Int] {
var factors : [Int] = Array<Int>()
for it in 1...number {
if isFactor(it) {
factors.append(it)
}
}
return factors
}
func sumFactors() -> Int {
let factors = getFactors()
var sum = 0
for factor in factors {
sum += factor
}
return sum
}
func isPrime() -> Bool {
return self.sumFactors() == number + 1
}
}
接著我們使用函數(shù)式寫法:
func isFactor(number: Int)(potential: Int) -> Bool {
return (number % potential) == 0
}
func factors(number: Int) -> [Int] {
let isFactorForNumber = isFactor(number)
return Array(1...number).filter {
isFactorForNumber(potential: $0)}
}
func sumFactors(number: Int) -> Int {
return factors(number).reduce(0){ (total, num) in
total + num }
}
func isPrime(number: Int) -> Bool {
return sumFactors(number) == number + 1
}
可以看到,我們定義了四個(gè)函數(shù),每個(gè)函數(shù)解決一個(gè)更小的問題。最后在isPrime
為起點(diǎn),把所有函數(shù)都串了起來,組成了整個(gè)算法實(shí)現(xiàn)。由于Swift中的函數(shù)都是一等函數(shù)。所以,我們可以使用filter和reduce這樣接受閉包的函數(shù)提供對篩選和求和更簡潔的表達(dá)方式。函數(shù)式寫法中,所有的函數(shù)都是無狀態(tài)的,無副作用的。也就是說無論你調(diào)用幾次,只要函數(shù)的輸入?yún)?shù)確定了,函數(shù)的輸出就確定了。由于無狀態(tài),這里的每個(gè)函數(shù)都是易于復(fù)用的。你可以在任何外部模塊放心地使用這些函數(shù),而不用像在面向?qū)ο笳Z言中那樣擔(dān)心對象的某個(gè)狀態(tài)會(huì)對你調(diào)用的函數(shù)產(chǎn)生影響。
函數(shù)式編程的核心是函數(shù),函數(shù)是“頭等公民”。這就像面向?qū)ο笳Z言的主要抽象方法是類。Swift中的函數(shù)具有函數(shù)式語言中的函數(shù)的所有特點(diǎn)。這種支持使得你可以很容易地使用Swift寫出函數(shù)式風(fēng)格的代碼。
原文出處:http://lincode.github.io/Swift-FirstOrder-Func
作者:LinGuo
更多建議: