swift3函數(shù)式編程

甩鍋申明(原文哪里的我忘記了筒饰,等會補上)

本文假設(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"))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齐邦,一起剝皮案震驚了整個濱河市椎侠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌措拇,老刑警劉巖我纪,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丐吓,居然都是意外死亡浅悉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門券犁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來术健,“玉大人,你說我怎么就攤上這事族操】良幔” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵色难,是天一觀的道長泼舱。 經(jīng)常有香客問我,道長枷莉,這世上最難降的妖魔是什么娇昙? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮笤妙,結(jié)果婚禮上冒掌,老公的妹妹穿的比我還像新娘噪裕。我一直安慰自己,他們只是感情好股毫,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布膳音。 她就那樣靜靜地躺著,像睡著了一般铃诬。 火紅的嫁衣襯著肌膚如雪祭陷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天趣席,我揣著相機與錄音兵志,去河邊找鬼。 笑死宣肚,一個胖子當(dāng)著我的面吹牛想罕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霉涨,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼按价,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了笙瑟?” 一聲冷哼從身側(cè)響起俘枫,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逮走,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體今阳,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡师溅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盾舌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墓臭。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妖谴,靈堂內(nèi)的尸體忽然破棺而出窿锉,到底是詐尸還是另有隱情,我是刑警寧澤膝舅,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布嗡载,位于F島的核電站,受9級特大地震影響仍稀,放射性物質(zhì)發(fā)生泄漏洼滚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一技潘、第九天 我趴在偏房一處隱蔽的房頂上張望遥巴。 院中可真熱鬧千康,春花似錦、人聲如沸铲掐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摆霉。三九已至豪椿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斯入,已是汗流浹背砂碉。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刻两,地道東北人增蹭。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像磅摹,于是被迫代替她去往敵國和親滋迈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容