高階函數
在Swift中,函數可做為“一等公民”的存在,也就意味着,我們可以和使用 int 以及 String 一樣,將函數當做 參數、值、類型來使用。
其中,將函數當作一個參數和值來使用可見下:
typealias addTwoInts = (Int,Int)->(Int) var funcType = addTwoInts.self func aAddb(a:Int,b:Int) -> Int { return a+b } func addFunc(_ add:addTwoInts,_ a:Int,_ b:Int) -> Int { return add(a,b) } //調用 self.addFunc(aAddb, 5, 6) // print --> 11
調用函數 “ self.addFunc(aAddb, 5, 6) ” 時候,aAddb就是一個典型的“值”, 盡管它實際上是一個函數。 與此同時, 它還做為addFunc的參數來使用。
雖然這看起來多此一舉,但實際這恰恰體現了高階函數的特點,犧牲一點點代碼的簡短,將重點體現在邏輯的清晰上。
一、一個高階函數的例子
我更喜歡叫下面的這個函數為高階函數:
var names:[String] = ["61","95","8","248","42"] //一個包含字符串的數組 names = names.sorted { (s1, s2) -> Bool in return s1<s2 } print(names) -----> ["248", "42", "61", "8", "95"]
這是一個排序函數。不要在意結果並沒有按照數字的大小排序,那是因為這是字符串排序,規則將按照首個字母的asc值進行比較。
先看看這個函數的原型:
public func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element]
這個函數顯然是將一個 (Element, Element) -> Bool 類型的函數做為他的參數。
一眼看過去,並不是那么好理解,來看下其內部的實現大概是這樣的:
extension Array{ typealias IncreasingOrder = (String,String) -> Bool mutating func mySorted(_ increasingOrder:IncreasingOrder) -> [String] { var newString:[String] = [String]() // 假設這里采用簡單選擇排序 for n in 0..<self.count{ var tamper:String = self[n] as! String for i in n+1..<self.count { // var next:String = self[i] as! String guard !increasingOrder(tamper,next) else { continue } swap(&tamper, &next) swap(&self[n], &self[i]) } newString.append(tamper) } return newString } }
調用:
names = ["61","95","8","248","42"] names = names.mySorted { (s1, s2) -> Bool in return s1<s2 } print(names) -----> ["248", "42", "61", "8", "95"]
這里為了簡便, 直接將Element替換成了String,針對String 類型來說,這個函數的功能和系統的Sotred的功能是一樣的。 如果需要支持更多的類型,可能要使用到泛型,甚至是where的可選綁定。其實系統的排序已經實現可選綁定式的排序了:重載了sorted函數,根據Element的不同類型,推斷是否需要進行可選綁定動作。
通過這個例子,可以看到,所謂的高階函數,其實就是將一個函數做為另一個函數的參數的語法。這個語法的基礎是Swift中的特性:函數的一等公民性質。
我們可以通過這種類型的語法,將類似的函數的內部代碼實現隱藏,只根據參數函數的值定義如何執行內部代碼。這中方式實現的代碼的靈活度將大大的提高。這為在Swift 中寫出使用一個函數替代多個同質化的函數提供了一種手段。 當然,做到這種效果可能還需要使用泛型編程。
函數嵌套
大部分情況下,遇到的所有功能都是全局函數,它們在全局范圍內定義。如果在函數局部定義一個函數,則稱為嵌套函數。
默認情況下,嵌套函數從外部世界隱藏,但在局部仍然可以正常低調用。一個閉包的函數也可以返回一個嵌套函數,以允許在其他范圍內使用嵌套函數。
func chooseStepFunction(backward: Bool) -> (Int) -> Int { //全局函數 func stepForward(input: Int) -> Int { return input + 1 } //嵌套函數 1 func stepBackward(input: Int) -> Int { return input - 1 } //嵌套函數 2 return backward ? stepBackward : stepForward //返回一個函數 }
調用:
var currentValue = -4 let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) // moveNearerToZero now refers to the nested stepForward() function while currentValue != 0 { print("\(currentValue)... ") ---->// -4...// -3...// -2...// -1...
currentValue = moveNearerToZero(currentValue) } print("zero!")// zero!
嵌套函數的本質是返回一個函數,之前所說的將函數當作參數基於同一個特性 -- 函數是swift的 一等公民。
同樣的道理,一個基本類型的既然能夠做為局部的變量來存在,函數為什么不行呢? 當然可以。這就是局部函數的由來,看來還是基於一等公民的身份啊。 函數嵌套將遵守與基本類型一樣的原則,局部的函數,職能夠在局部去訪問,在外部是沒有效果的。
如果我們將嵌套的函數匿名的話,也即是我們下面的這種形式:
typealias adds = (Int)->(Int) func add(_ c:Int) -> adds { return { a in return a + c } }
調用:
let addStart = self.add(0) let addTwo = addStart(2) let addFive = addStart(5) print(addTwo) -----> 2 print(addFive) -----> 5
在add函數中,定義了一個起始的數值: 0,返回一個adds類型的函數。 之后我們可以通過給adds類型的實例 addTwo和addFive傳遞相應的參數即可實現多個函數的套用。
比如,如果我們在做一個以一個起始值做為加減的時候,這中用法就靈活很多,比如,如果我需要以 10做為初始值:
let add = self.add(10) let addTwo = add(2) let addFive = add(5) print(addTwo) ==> 12 print(addFive) --> 15
add已經是另一個函數了。
而實際上,這就函數嵌套的另一種使用,這有很多的叫法 ,我更願意叫它 -- 柯里化。
柯里化
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。
在一般函數中,並不能輕易做到柯里化,可能需要借助其他的方案,比如,將多個參數使用替換成一個struct或者class。
然而高階函數和嵌套函數卻可以很輕易的做到,並保證代碼的邏輯清晰。然而為什么要使用柯里化,可以閱讀這個文章。 私人覺得,柯里化的優勢是去同質化的代碼 以及 注重函數的實現減少參數的干擾,簡潔提升邏輯。上面的例子剛好解釋了柯里化的使用。
