甩鍋申明(原文哪里的我忘記了,等會補上)
本文假設你熟悉swift3
關于函數式編程的介紹
FP(Functional programming)是一種編程范式。與聲明式編程不同的是,它強調不可變性(immutable),純函數(pure function),引用透明(referentially transparent)等等。如果你看到這些術語一臉懵逼,請不要灰心。本文的目的就是介紹這些。swift具有相當多的FP特性,你不應該對這些優秀的性質視而不見。
1.函數是一等公民
什么是一等公民呢(first class),指的是函數與其他數據類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數,傳入另一個函數,或者作為別的函數的返回值。(作者原文說所有語言都是這樣)
so 我們可以這樣
func bar() {
print("foo")
}
let baz = bar
baz() // prints "foo"
也可以這樣
func foo() {
print("foo")
}
func bar() {
print("bar")
}
func baz() {
print("baz")
}
let functions = [
foo,
bar,
baz
]
for function in functions {
function() // runs all functions
}
是的,你沒看錯,函數也能丟到數組里面,你可以做一大堆很cooooool的事情在這種語言里
順便我們現在來定義一個概念:
Anynonmous(匿名函數) functions a.k.a Closures(閉包)
閉包(closure)是所有函數式語言都具備的一項平常特性,可是相關的論述卻常常充斥著晦澀乃至神秘的字眼。所謂閉包,實際上是一種特殊的函數,它在暗地里綁定了函數內部引用的所有變量。換句話說,這種函數(或方法)把它引用的所有東西都放在一個上下文里“包”了起來(原文解釋得太簡單了)。
閉包在swift有很多種定義的方式(我數了下至少有5,6種吧),最常見的一種方式是
{(parameter1 : Type, parameter2: Type, ..., parameterN: Type) -> ReturnType in
}
他們也可以定義成
let anonymous = { (item: Int) -> Bool in
return item > 2
}
anonymous(3)
哇哇 你不是說函數是匿名的嗎,現在怎么有個名字了。
他們確實沒有名字,這里只是把一個匿名函數賦值給了一個變量,就像我們之前干的那樣,你看下面代碼。
({ (item: Int) -> Bool in
return item > 2
}(4)) // returns true(有點像js的立即執行函數)
現在他是一個真正的匿名函數了,我們在定義的時候同時也執行了這個函數。同時值得注意的是內個匿名函數都有他自己的函數類型,函數類型大概看起來是這樣的。
let anon : (Int, Bool) -> String = { input,condition in
if condition { return String(input) }
return "Failed"
}
anon(12, true) // returns "12"
anon(12, false) // returns "Failed"
其中的我們定義的類型(可以省略,但是不建議,xcode在解析復雜類型時候會白板):
(Int, Bool) -> String
這表示這個函數接受一個Int和BooL倆個參數并且返回一個String類型,由于函數有類型,編譯器在編譯的時候會確保類型安全,你可以不寫類型,但是類型一定要對(非常不建議不寫類型,xcode在解析復雜類型時候會白板):
你可以這么寫
var functionArray = [(Int, Bool)->String]()
let one : (Int, Bool) -> String = { a, b in
if b { return "\(a)"}
else { return "Fail" }
}
functionArray.append(one)
let two = { (a: Int, b: Bool) -> String in
if b { return "\(a)" }
else { return "FailTwo" }
}
functionArray.append(two)
上面的代碼都可以工作,但是你不能這寫:
let three : (Int)->Bool = { item in
return item > 2
}
functionArray.append(three)
你會得到錯誤信息:
ERROR at line 21, col 22: cannot convert value of type '(Int) -> Bool' to expected argument type '(Int, Bool) -> String'
functionArray.append(three)
大概意思就是類型不對應,你寫swift代碼時候將會大量面對這這種類型問題,會持續的折磨你。
這種類型語法有時候顯得很啰嗦,我們可以用typealias操作符來重新定義一個復雜的類型。
typealias Checker = (Int, Bool) -> String
var funcArray = [Checker]()
let one : Checker = { a, b in
if b { return "\(a)" }
return "Fail"
}
funcArray.append(one)
還有個額外的語法糖,在閉包中可以用$0,$1,$2等來替代函數參數,這種簡潔的表達形式,會使我們的代碼看上去很干凈。現在改寫下one函數
let two: Checker = { return $1 ? "\($0)" : "Fail" }
這里面$0,來替代Int,$1替代了Bool
如果你的函數只有一行return也可以不寫
2 pure function
純函數的概念很簡單 你可以簡單理解為一個數學函數y=f(x)(使對于集合A中的任意一個數x,在集合B中都有唯一確定的數和它對應),也就是說對于一個給定的輸入x,函數的輸出是唯一的,不依賴于外部狀態。同時也不會改變外部的狀態。
例如:
func countUp(currentCount: Int) -> Int {
return currentCount + 1
}
這就是個純函數
var counter = 0
func countUp() -> Int{
counter += 1
return counter
}
但是他不是,他改變了外部的狀態。純函數,變量的不可變性會讓你的測試代碼變得很容易編寫。同時他也很適合并發編程,變量的狀態只依賴于他創建的時刻,再也不需要那些幺蛾子的臨界區這種東西了。
3. Higher order functions
簡而言之,高階函數就是將其他函數作為參數或者返回類型是一個函數的函數,有了他,你再也不用知道數據是從哪里來了,每一個函數都是為了用小函數組織成更大的函數,函數的參數也是函數,函數返回的也是函數,最后得到一個超級牛逼的函數,最后數據灌進去了。
舉個栗子
func filter(sequence: [Int], elementsSmallerThan border: Int) -> [Int] {
var newSequence = [Int]()
for item in sequence {
if item < border {
newSequence.append(item)
}
}
return newSequence
}
filter(sequence: [1,2,3,4], elementsSmallerThan: 3) // returns [1,2]
這個函數篩選比3小的,現在pm改需求了,要比2大。沒事在寫一個
func filter(sequence: [Int], elementsLargerThan border: Int) -> [Int] {
var newSequence = [Int]()
for item in sequence {
if item > border {
newSequence.append(item)
}
}
return newSequence
}
filter(sequence: [1,2,3,4], elementsLargerThan: 2) // returns [3,4]
要是在改需求呢?難道繼續改代碼嗎
看看用高階函數是怎么做的
func filter(sequence: [Int], condition: (Int)->Bool) -> [Int] {
var newSequence = [Int]()
for item in sequence {
if condition(item) {
newSequence.append(item)
}
}
return newSequence
}
let sequence = [1,2,3,4]
let smaller = filter(sequence: sequence, condition: { item in
item < 3
})
let larger = filter(sequence: sequence, condition: { item in
item > 2
})
print(smaller) // prints [1,2]
print(larger) // prints [3,4]
現在隨便PM怎么改需求,我們只要傳遞一個函數進去就行了
我們也可以用swift尾閉包的語法糖
// 大概就是閉包是最后一個參數,可以這么寫
let equalTo = filter(sequence: sequence) { item in item == 2}
// 一個意思,語法糖而已
let isOne = filter(sequence: sequence) { $0 == 1}
上面寫法其實也有很大的局限,因為你只能傳入一個函數,不能傳遞一個帶參數的函數,這么說有點抽象,請對著代碼理解,下面代碼其實使用了柯里化。
typealias Modifier = ([Int]) -> [Int]
func chooseModifier(isHead: Bool) -> Modifier {
if isHead {
return { array in
Array(array.dropLast(1))
}
} else {
return { array in
[array[0]]
}
}
}
let head = chooseModifier(isHead: true)
let tail = chooseModifier(isHead: false)
//這個函數干的事情和函數的命名我有點蒙蔽....
//現在head和tail就是一個函數了,他們的函數簽名是([])->[]
//head([1,2,3]) tail([1,2,3])自己試試看效果
我們來定義一個檢測一個數時候某個range里的函數
typealias Range = (Int) -> Bool
func range(start: Int, end: Int) -> Range {
return { point in
return (point > start) && (point < end)
}
}
let fourToFifteen = range(start: 4, end: 15)
//Check if a number belongs to a range
print(fourToFifteen(14)) //true
print(fourToFifteen(16)) //false 和上面同理
Conditional parameter evaluation
現在有種情況,你需要根據用的選擇來生成一個參數,但是計算的代價非常昂貴。不用擔心你現在可以使用高階函數來使計算推遲到你確定需要使用時,
func stringify1(condition: Bool, parameter: Int) -> String {
if condition {
return "\(parameter)"
}
return "Fail"
}
func stringify2(condition: Bool, parameter: ()->Int) -> String{
if condition {
return "\(parameter())"
}
return "Fail"
}
let a = stringify1(condition: false, parameter: expensiveOperation())
let b = stringify2(condition: false, parameter: { return expensiveOperation()})
第一種是我們常用的形式,你只要調用這個函數就會調用判斷函數。
第二種是高階函數版本,你不走這個判斷分支,判斷函數是不會走的。
這種凌亂的寫法會讓人很難掌握,但是swift有個內置的機制來幫我們處理這些亂七八糟的類型,不要你操心
@autoclosure annotation
恩,上面說的就是@autoclosure ,他會自動的把一個表達式轉換成一個closure
上代碼
func stringify3(condition: Bool, parameter: @autoclosure ()->Int) -> String {
if condition {
return "\(parameter())"
}
return "Fail"
}
現在我們如果想調用stringify3:
stringify3(condition: true, parameter: 12)
上面代碼有點不好理解 我在舉個栗子
func test(_ p:()->Bool) {
if p() {
}
}
test({return 3>1})
test({ 3>1})
test{ 3>1}
func test1( _ p:@autoclosure ()->Bool) {
if p() {
}
}
test1(3>1)
請自己體會下,這有點抽象。。。
4. Currying
柯里化最大的好處是可以把多參數函數映射到單參數函數,把一個非常復雜的函數分解成一個個簡單函數
來看一個最基本的栗子
func add(_ a: Int) -> ((Int)-> Int) {
return { b in
return a + b
}
}
add(2)(3) // returns 5
定義個很普通的add函數,然后調用add(2)(3)這不是什么奇怪的語法糖,而是add(2)本來就是個函數,你可以這么理解
let function = add(2)
function(3)
繼續看代碼
typealias Modifier = (String)->String
func uppercase() -> Modifier {
return { string in
return string.uppercased()
}
}
func removeLast() -> Modifier {
return { string in
return String(string.characters.dropLast())
}
}
func addSuffix(suffix: String) -> Modifier {
return { string in
return string + suffix
}
}
定義了一大丟亂七八糟的函數 返回值都是(string)-> string
let uppercased = uppercase()("Value")
let removed = removeLast()(uppercased)
let withSuffix = addSuffix("")(removed)
用科里化的寫法
let a = addSuffix(suffix: "suffix")(removeLast()(uppercase()("IntitialFunction")))
是不是很神奇?下面的這段有點抽象,有個很炫酷的學術名字compose monad,不要理解這是什么東西,只要知道他是把倆個函數組合在了一起。
func compose(_ left: @escaping Modifier, _ right: @escaping Modifier) -> Modifier {
return { string in
left(right(string))
}
}
現在函數就長這樣了
let a = compose(compose(uppercase(), removeLast()), addSuffix(suffix: "Abc"))("IntitialValue")
這東西還是太難懂了,太多的括號看著礙眼,有倆個方法一個是定義個科里化操作符,一種是將Modifier裝在一個容器中
struct Modifier {
private let modifier: (String) -> String
init() {
self.modifier = { return $0 }
}
private init(modifier: @escaping (String)->String) {
self.modifier = modifier
}
var uppercase: Modifier {
return Modifier(modifier: { string in
self.modifier(string).uppercased()
})
}
var removeLast : Modifier {
return Modifier(modifier: { string in
return String(self.modifier(string).characters.dropLast())
})
}
func add(suffix: String) -> Modifier {
return Modifier(modifier: { string in
return self.modifier(string) + suffix
})
}
func modify(_ input: String) -> String {
return self.modifier(input)
}
}
// The call is now clean and clearly states which actions happen in
// which order
let modified = Modifier().uppercase.removeLast.add(suffix: "suffix").modify("InputValue")
print(modified)
有了這個Modifier還能這么玩
let modified = Modifier().add(suffix: "Initial").uppercase.add(suffix: "Later").removeLast.removeLast.removeLast.add(suffix: "Other").modify("Value")
The last example which uses struct breaks the functional pattern a little bit since it holds the private modifier variable. It sacrifices some of the safety for a little bit of syntactic sugar. (沒理解),下面用科里化操作符來重寫一次,他看起來是
ypealias Modifier = (String)->String
func uppercase() -> Modifier {
return { string in
return string.uppercased()
}
}
func removeLast() -> Modifier {
return { string in
return String(string.characters.dropLast())
}
}
func addSuffix(suffix: String) -> Modifier {
return { string in
return string + suffix
}
}
precedencegroup CurryPrecedence {
associativity: left
}
infix operator |> : CurryPrecedence
func |> ( left: @escaping Modifier, right: @escaping Modifier) -> Modifier {
return { string in
right(left(string))
}
}
let modified = uppercase() |> removeLast() |> addSuffix(suffix: "123")
print(modified("Abcd"))