甩鍋申明(原文哪里的我忘記了筒饰,等會補上)
本文假設(shè)你熟悉swift3
關(guān)于函數(shù)式編程的介紹
FP(Functional programming)是一種編程范式罚勾。與聲明式編程不同的是,它強調(diào)不可變性(immutable)租幕,純函數(shù)(pure function),引用透明(referentially transparent)等等。如果你看到這些術(shù)語一臉懵逼戒职,請不要灰心。本文的目的就是介紹這些透乾。swift具有相當(dāng)多的FP特性洪燥,你不應(yīng)該對這些優(yōu)秀的性質(zhì)視而不見。
1.函數(shù)是一等公民
什么是一等公民呢(first class)乳乌,指的是函數(shù)與其他數(shù)據(jù)類型一樣捧韵,處于平等地位,可以賦值給其他變量汉操,也可以作為參數(shù)再来,傳入另一個函數(shù),或者作為別的函數(shù)的返回值磷瘤。(作者原文說所有語言都是這樣)
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
}
是的芒篷,你沒看錯,函數(shù)也能丟到數(shù)組里面膀斋,你可以做一大堆很cooooool的事情在這種語言里
順便我們現(xiàn)在來定義一個概念:
Anynonmous(匿名函數(shù)) functions a.k.a Closures(閉包)
閉包(closure)是所有函數(shù)式語言都具備的一項平常特性梭伐,可是相關(guān)的論述卻常常充斥著晦澀乃至神秘的字眼。所謂閉包仰担,實際上是一種特殊的函數(shù)糊识,它在暗地里綁定了函數(shù)內(nèi)部引用的所有變量绩社。換句話說,這種函數(shù)(或方法)把它引用的所有東西都放在一個上下文里“包”了起來(原文解釋得太簡單了)赂苗。
閉包在swift有很多種定義的方式(我數(shù)了下至少有5愉耙,6種吧),最常見的一種方式是
{(parameter1 : Type, parameter2: Type, ..., parameterN: Type) -> ReturnType in
}
他們也可以定義成
let anonymous = { (item: Int) -> Bool in
return item > 2
}
anonymous(3)
哇哇 你不是說函數(shù)是匿名的嗎拌滋,現(xiàn)在怎么有個名字了朴沿。
他們確實沒有名字,這里只是把一個匿名函數(shù)賦值給了一個變量败砂,就像我們之前干的那樣,你看下面代碼赌渣。
({ (item: Int) -> Bool in
return item > 2
}(4)) // returns true(有點像js的立即執(zhí)行函數(shù))
現(xiàn)在他是一個真正的匿名函數(shù)了,我們在定義的時候同時也執(zhí)行了這個函數(shù)昌犹。同時值得注意的是內(nèi)個匿名函數(shù)都有他自己的函數(shù)類型坚芜,函數(shù)類型大概看起來是這樣的。
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在解析復(fù)雜類型時候會白板):
(Int, Bool) -> String
這表示這個函數(shù)接受一個Int和BooL倆個參數(shù)并且返回一個String類型,由于函數(shù)有類型铸敏,編譯器在編譯的時候會確保類型安全缚忧,你可以不寫類型,但是類型一定要對(非常不建議不寫類型杈笔,xcode在解析復(fù)雜類型時候會白板):
你可以這么寫
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)
大概意思就是類型不對應(yīng),你寫swift代碼時候?qū)罅棵鎸@這種類型問題桩撮,會持續(xù)的折磨你敦第。
這種類型語法有時候顯得很啰嗦,我們可以用typealias操作符來重新定義一個復(fù)雜的類型店量。
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等來替代函數(shù)參數(shù),這種簡潔的表達形式融师,會使我們的代碼看上去很干凈∮壹兀現(xiàn)在改寫下one函數(shù)
let two: Checker = { return $1 ? "\($0)" : "Fail" }
這里面$0,來替代Int,$1替代了Bool
如果你的函數(shù)只有一行return也可以不寫
2 pure function
純函數(shù)的概念很簡單 你可以簡單理解為一個數(shù)學(xué)函數(shù)y=f(x)(使對于集合A中的任意一個數(shù)x旱爆,在集合B中都有唯一確定的數(shù)和它對應(yīng))舀射,也就是說對于一個給定的輸入x,函數(shù)的輸出是唯一的,不依賴于外部狀態(tài)。同時也不會改變外部的狀態(tài)盅抚。
例如:
func countUp(currentCount: Int) -> Int {
return currentCount + 1
}
這就是個純函數(shù)
var counter = 0
func countUp() -> Int{
counter += 1
return counter
}
但是他不是,他改變了外部的狀態(tài)邢羔。純函數(shù)驼抹,變量的不可變性會讓你的測試代碼變得很容易編寫。同時他也很適合并發(fā)編程拜鹤,變量的狀態(tài)只依賴于他創(chuàng)建的時刻框冀,再也不需要那些幺蛾子的臨界區(qū)這種東西了。
3. Higher order functions
簡而言之敏簿,高階函數(shù)就是將其他函數(shù)作為參數(shù)或者返回類型是一個函數(shù)的函數(shù)明也,有了他,你再也不用知道數(shù)據(jù)是從哪里來了惯裕,每一個函數(shù)都是為了用小函數(shù)組織成更大的函數(shù)温数,函數(shù)的參數(shù)也是函數(shù),函數(shù)返回的也是函數(shù)轻猖,最后得到一個超級牛逼的函數(shù)帆吻,最后數(shù)據(jù)灌進去了。
舉個栗子
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]
這個函數(shù)篩選比3小的咙边,現(xiàn)在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]
要是在改需求呢败许?難道繼續(xù)改代碼嗎
看看用高階函數(shù)是怎么做的
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]
現(xiàn)在隨便PM怎么改需求,我們只要傳遞一個函數(shù)進去就行了
我們也可以用swift尾閉包的語法糖
// 大概就是閉包是最后一個參數(shù)淑蔚,可以這么寫
let equalTo = filter(sequence: sequence) { item in item == 2}
// 一個意思市殷,語法糖而已
let isOne = filter(sequence: sequence) { $0 == 1}
上面寫法其實也有很大的局限,因為你只能傳入一個函數(shù)刹衫,不能傳遞一個帶參數(shù)的函數(shù)醋寝,這么說有點抽象,請對著代碼理解带迟,下面代碼其實使用了柯里化音羞。
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)
//這個函數(shù)干的事情和函數(shù)的命名我有點蒙蔽....
//現(xiàn)在head和tail就是一個函數(shù)了,他們的函數(shù)簽名是([])->[]
//head([1,2,3]) tail([1,2,3])自己試試看效果
我們來定義一個檢測一個數(shù)時候某個range里的函數(shù)
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
現(xiàn)在有種情況仓犬,你需要根據(jù)用的選擇來生成一個參數(shù)嗅绰,但是計算的代價非常昂貴。不用擔(dān)心你現(xiàn)在可以使用高階函數(shù)來使計算推遲到你確定需要使用時搀继,
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()})
第一種是我們常用的形式窘面,你只要調(diào)用這個函數(shù)就會調(diào)用判斷函數(shù)。
第二種是高階函數(shù)版本叽躯,你不走這個判斷分支财边,判斷函數(shù)是不會走的。
這種凌亂的寫法會讓人很難掌握点骑,但是swift有個內(nèi)置的機制來幫我們處理這些亂七八糟的類型酣难,不要你操心
@autoclosure annotation
恩谍夭,上面說的就是@autoclosure ,他會自動的把一個表達式轉(zhuǎn)換成一個closure
上代碼
func stringify3(condition: Bool, parameter: @autoclosure ()->Int) -> String {
if condition {
return "\(parameter())"
}
return "Fail"
}
現(xiàn)在我們?nèi)绻胝{(diào)用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
柯里化最大的好處是可以把多參數(shù)函數(shù)映射到單參數(shù)函數(shù)齐板,把一個非常復(fù)雜的函數(shù)分解成一個個簡單函數(shù)
來看一個最基本的栗子
func add(_ a: Int) -> ((Int)-> Int) {
return { b in
return a + b
}
}
add(2)(3) // returns 5
定義個很普通的add函數(shù),然后調(diào)用add(2)(3)這不是什么奇怪的語法糖,而是add(2)本來就是個函數(shù),你可以這么理解
let function = add(2)
function(3)
繼續(xù)看代碼
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
}
}
定義了一大丟亂七八糟的函數(shù) 返回值都是(string)-> string
let uppercased = uppercase()("Value")
let removed = removeLast()(uppercased)
let withSuffix = addSuffix("")(removed)
用科里化的寫法
let a = addSuffix(suffix: "suffix")(removeLast()(uppercase()("IntitialFunction")))
是不是很神奇葛菇?下面的這段有點抽象甘磨,有個很炫酷的學(xué)術(shù)名字compose monad,不要理解這是什么東西眯停,只要知道他是把倆個函數(shù)組合在了一起济舆。
func compose(_ left: @escaping Modifier, _ right: @escaping Modifier) -> Modifier {
return { string in
left(right(string))
}
}
現(xiàn)在函數(shù)就長這樣了
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"))