swift3函數式編程

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

本文假設你熟悉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"))
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容