swift3函數(shù)式編程

甩鍋申明(原文哪里的我忘記了,等會(huì)補(bǔ)上)

本文假設(shè)你熟悉swift3

關(guān)于函數(shù)式編程的介紹

FP(Functional programming)是一種編程范式。與聲明式編程不同的是,它強(qiáng)調(diào)不可變性(immutable),純函數(shù)(pure function),引用透明(referentially transparent)等等。如果你看到這些術(shù)語(yǔ)一臉懵逼,請(qǐng)不要灰心。本文的目的就是介紹這些。swift具有相當(dāng)多的FP特性,你不應(yīng)該對(duì)這些優(yōu)秀的性質(zhì)視而不見(jiàn)。

1.函數(shù)是一等公民

什么是一等公民呢(first class),指的是函數(shù)與其他數(shù)據(jù)類(lèi)型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數(shù),傳入另一個(gè)函數(shù),或者作為別的函數(shù)的返回值。(作者原文說(shuō)所有語(yǔ)言都是這樣)
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
}

是的,你沒(méi)看錯(cuò),函數(shù)也能丟到數(shù)組里面,你可以做一大堆很cooooool的事情在這種語(yǔ)言里
順便我們現(xiàn)在來(lái)定義一個(gè)概念:
Anynonmous(匿名函數(shù)) functions a.k.a Closures(閉包)
閉包(closure)是所有函數(shù)式語(yǔ)言都具備的一項(xiàng)平常特性,可是相關(guān)的論述卻常常充斥著晦澀乃至神秘的字眼。所謂閉包,實(shí)際上是一種特殊的函數(shù),它在暗地里綁定了函數(shù)內(nèi)部引用的所有變量。換句話(huà)說(shuō),這種函數(shù)(或方法)把它引用的所有東西都放在一個(gè)上下文里“包”了起來(lái)(原文解釋得太簡(jiǎn)單了)。
閉包在swift有很多種定義的方式(我數(shù)了下至少有5,6種吧),最常見(jiàn)的一種方式是

{(parameter1 : Type, parameter2: Type, ..., parameterN: Type) -> ReturnType in 

}

他們也可以定義成

let anonymous = { (item: Int) -> Bool in 
    return item > 2
}

anonymous(3)

哇哇 你不是說(shuō)函數(shù)是匿名的嗎,現(xiàn)在怎么有個(gè)名字了。
他們確實(shí)沒(méi)有名字,這里只是把一個(gè)匿名函數(shù)賦值給了一個(gè)變量,就像我們之前干的那樣,你看下面代碼。

({ (item: Int) -> Bool in 
    return item > 2
}(4)) // returns true(有點(diǎn)像js的立即執(zhí)行函數(shù))

現(xiàn)在他是一個(gè)真正的匿名函數(shù)了,我們?cè)诙x的時(shí)候同時(shí)也執(zhí)行了這個(gè)函數(shù)。同時(shí)值得注意的是內(nèi)個(gè)匿名函數(shù)都有他自己的函數(shù)類(lèi)型,函數(shù)類(lèi)型大概看起來(lái)是這樣的。

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"

其中的我們定義的類(lèi)型(可以省略,但是不建議,xcode在解析復(fù)雜類(lèi)型時(shí)候會(huì)白板):

(Int, Bool) -> String

這表示這個(gè)函數(shù)接受一個(gè)Int和BooL倆個(gè)參數(shù)并且返回一個(gè)String類(lèi)型,由于函數(shù)有類(lèi)型,編譯器在編譯的時(shí)候會(huì)確保類(lèi)型安全,你可以不寫(xiě)類(lèi)型,但是類(lèi)型一定要對(duì)(非常不建議不寫(xiě)類(lèi)型,xcode在解析復(fù)雜類(lèi)型時(shí)候會(huì)白板):
你可以這么寫(xiě)

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)

上面的代碼都可以工作,但是你不能這寫(xiě):

let three : (Int)->Bool = { item in 
    return item > 2
}

functionArray.append(three)

你會(huì)得到錯(cuò)誤信息:

ERROR at line 21, col 22: cannot convert value of type '(Int) -> Bool' to expected argument type '(Int, Bool) -> String'
functionArray.append(three)

大概意思就是類(lèi)型不對(duì)應(yīng),你寫(xiě)swift代碼時(shí)候?qū)?huì)大量面對(duì)這這種類(lèi)型問(wèn)題,會(huì)持續(xù)的折磨你。
這種類(lèi)型語(yǔ)法有時(shí)候顯得很啰嗦,我們可以用typealias操作符來(lái)重新定義一個(gè)復(fù)雜的類(lèi)型。

typealias Checker = (Int, Bool) -> String

var funcArray = [Checker]()

let one : Checker = { a, b in 
    if b { return "\(a)" }
    return "Fail"
}

funcArray.append(one)

還有個(gè)額外的語(yǔ)法糖,在閉包中可以用$0,$1,$2等來(lái)替代函數(shù)參數(shù),這種簡(jiǎn)潔的表達(dá)形式,會(huì)使我們的代碼看上去很干凈?,F(xiàn)在改寫(xiě)下one函數(shù)

let two: Checker = { return $1 ? "\($0)" :  "Fail" }

這里面$0,來(lái)替代Int,$1替代了Bool
如果你的函數(shù)只有一行return也可以不寫(xiě)

2 pure function

純函數(shù)的概念很簡(jiǎn)單 你可以簡(jiǎn)單理解為一個(gè)數(shù)學(xué)函數(shù)y=f(x)(使對(duì)于集合A中的任意一個(gè)數(shù)x,在集合B中都有唯一確定的數(shù)和它對(duì)應(yīng)),也就是說(shuō)對(duì)于一個(gè)給定的輸入x,函數(shù)的輸出是唯一的,不依賴(lài)于外部狀態(tài)。同時(shí)也不會(huì)改變外部的狀態(tài)。
例如:

func countUp(currentCount: Int) -> Int {
    return currentCount + 1
}

這就是個(gè)純函數(shù)

var counter = 0
func countUp() -> Int{
    counter += 1
    return counter
}

但是他不是,他改變了外部的狀態(tài)。純函數(shù),變量的不可變性會(huì)讓你的測(cè)試代碼變得很容易編寫(xiě)。同時(shí)他也很適合并發(fā)編程,變量的狀態(tài)只依賴(lài)于他創(chuàng)建的時(shí)刻,再也不需要那些幺蛾子的臨界區(qū)這種東西了。

3. Higher order functions

簡(jiǎn)而言之,高階函數(shù)就是將其他函數(shù)作為參數(shù)或者返回類(lèi)型是一個(gè)函數(shù)的函數(shù),有了他,你再也不用知道數(shù)據(jù)是從哪里來(lái)了,每一個(gè)函數(shù)都是為了用小函數(shù)組織成更大的函數(shù),函數(shù)的參數(shù)也是函數(shù),函數(shù)返回的也是函數(shù),最后得到一個(gè)超級(jí)牛逼的函數(shù),最后數(shù)據(jù)灌進(jìn)去了。
舉個(gè)栗子

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]

這個(gè)函數(shù)篩選比3小的,現(xiàn)在pm改需求了,要比2大。沒(méi)事在寫(xiě)一個(gè)

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怎么改需求,我們只要傳遞一個(gè)函數(shù)進(jìn)去就行了
我們也可以用swift尾閉包的語(yǔ)法糖

// 大概就是閉包是最后一個(gè)參數(shù),可以這么寫(xiě)
let equalTo = filter(sequence: sequence) { item in item == 2}

// 一個(gè)意思,語(yǔ)法糖而已
let isOne = filter(sequence: sequence) { $0 == 1}

上面寫(xiě)法其實(shí)也有很大的局限,因?yàn)槟阒荒軅魅胍粋€(gè)函數(shù),不能傳遞一個(gè)帶參數(shù)的函數(shù),這么說(shuō)有點(diǎn)抽象,請(qǐng)對(duì)著代碼理解,下面代碼其實(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)
//這個(gè)函數(shù)干的事情和函數(shù)的命名我有點(diǎn)蒙蔽....
//現(xiàn)在head和tail就是一個(gè)函數(shù)了,他們的函數(shù)簽名是([])->[]
//head([1,2,3]) tail([1,2,3])自己試試看效果

我們來(lái)定義一個(gè)檢測(cè)一個(gè)數(shù)時(shí)候某個(gè)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ù)用的選擇來(lái)生成一個(gè)參數(shù),但是計(jì)算的代價(jià)非常昂貴。不用擔(dān)心你現(xiàn)在可以使用高階函數(shù)來(lái)使計(jì)算推遲到你確定需要使用時(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)用這個(gè)函數(shù)就會(huì)調(diào)用判斷函數(shù)。
第二種是高階函數(shù)版本,你不走這個(gè)判斷分支,判斷函數(shù)是不會(huì)走的。
這種凌亂的寫(xiě)法會(huì)讓人很難掌握,但是swift有個(gè)內(nèi)置的機(jī)制來(lái)幫我們處理這些亂七八糟的類(lèi)型,不要你操心
@autoclosure annotation
恩,上面說(shuō)的就是@autoclosure ,他會(huì)自動(dòng)的把一個(gè)表達(dá)式轉(zhuǎn)換成一個(gè)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)

上面代碼有點(diǎn)不好理解 我在舉個(gè)栗子


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)

請(qǐng)自己體會(huì)下,這有點(diǎn)抽象。。。

4. Currying

柯里化最大的好處是可以把多參數(shù)函數(shù)映射到單參數(shù)函數(shù),把一個(gè)非常復(fù)雜的函數(shù)分解成一個(gè)個(gè)簡(jiǎn)單函數(shù)
來(lái)看一個(gè)最基本的栗子

func add(_ a: Int) -> ((Int)-> Int) {
    return { b in 
        return a + b
    }
}

add(2)(3) // returns 5

定義個(gè)很普通的add函數(shù),然后調(diào)用add(2)(3)這不是什么奇怪的語(yǔ)法糖,而是add(2)本來(lái)就是個(gè)函數(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)

用科里化的寫(xiě)法

let a = addSuffix(suffix: "suffix")(removeLast()(uppercase()("IntitialFunction")))

是不是很神奇?下面的這段有點(diǎn)抽象,有個(gè)很炫酷的學(xué)術(shù)名字compose monad,不要理解這是什么東西,只要知道他是把倆個(gè)函數(shù)組合在了一起。

func compose(_ left: @escaping Modifier, _ right: @escaping Modifier) -> Modifier {
    return { string in 
        left(right(string))
    }
}

現(xiàn)在函數(shù)就長(zhǎng)這樣了

let a = compose(compose(uppercase(), removeLast()), addSuffix(suffix: "Abc"))("IntitialValue")

這東西還是太難懂了,太多的括號(hào)看著礙眼,有倆個(gè)方法一個(gè)是定義個(gè)科里化操作符,一種是將Modifier裝在一個(gè)容器中

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)

有了這個(gè)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. (沒(méi)理解),下面用科里化操作符來(lái)重寫(xiě)一次,他看起來(lái)是

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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容