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