[連載]Swift開發入門(06)--- 函數式編程

來源:互聯網
上載者:User

標籤:swift   函數式編程   柯裡化   

??物件導向編程和函數式編程是目前最主流的兩種編程範式,而關於這兩種範式孰優孰劣的討論一直都沒有停止過。事實上,真正理解兩種編程範式的程式員不會武斷的說這二者孰優孰劣,因為任何程式設計語言都沒有什麼靈丹妙藥讓其使用者成為優秀的程式員。其實,像Java這樣很經典的物件導向的程式設計語言,也能夠看到函數式編程的影子,如果你使用過訪問者模式、命令模式,如果你使用過介面回調,你實際上已經使用了函數式編程的理念,而且在新版本的Java中,已經開始支援Lambda運算式和函數式介面,這些都是Java為了支援函數式編程所作出的改進。同樣,我們也可以用C語言寫出物件導向風格的代碼。其實,只要在適當的地方使用適當的編程範式就能夠寫出優質的代碼,我們不應該讓自己的程式囿於某一種編程範式,就如同一個優秀的程式員絕不會聲稱自己效忠於某種語言(有很多蹩腳的三流程式員就會做這樣的事情)。

簡單數組過濾

??讓我們看這樣一個例子,在一個數組中放入一組偶數,傳統的做法是這樣的。

var evens = [Int]()for i in 1...10 {    if i % 2 == 0 {        evens.append(i)    }}println(evens)      // [2, 4, 6, 8, 10]

??如果用函數編程的方式,我們可以用下面的代碼來做同樣的事情。而且我們將判斷偶數的代碼寫成一個函數,明顯增加了代碼的可重用性。

func isEven(number: Int) -> Bool {    return number % 2 == 0}var evens = Array(1...10).filter(isEven)println(evens)

??當然,也可以寫成下面的樣子。

var evens = Array(1...10).filter{ $0 % 2 == 0 }println(evens)      // [2, 4, 6, 8, 10]

??明顯,函數式編程有如下一些特性:
?? - 高階函數:函數可以作為函數的參數傳給函數。
?? - 一等公民:可以將函數視為變數來使用。
?? - 閉包:可以使用匿名函數。

歸約(Reducing)

??如果要在上面的例子再增加一項功能,將數組中的偶數求和,傳統的做法如下所示。

var evens = [Int]()for i in 1...10 {    if i % 2 == 0 {        evens.append(i)    }}var evenSum = 0for i in evens {    evenSum += i}println(evenSum)    // 30

??用函數式編程的方式重新編寫上面的代碼,如下所示。

var evenSum = Array(1...10)    .filter { $0 % 2 == 0}    .reduce(0) { $0 + $1 }println(evenSum)    // 30

??如果想理解reduce是如何工作的,可以看看函數的原型。

func reduce<U>(initial: U, combine: @noescape (U, T) -> U) -> U
索引器

??我們再來完成一個新的任務,為數組中的元素建立索引。假如有一個數組中有一系列的字串,我們希望通過字串的首字母作為索引來建立一個新的儲存結構,傳統的做法如下所示。

import Foundationlet words = [    "Cat", "Chicken", "Fish", "Dog", "Mouse",  "Pig", "Monkey"]typealias Entry = (Character, [String])func buildIndex(words: [String]) -> [Entry] {    var result = [Entry]()    var letters = [Character]()    for word in words {        let firstLetter = Character(word.substringToIndex(            advance(word.startIndex, 1)).uppercaseString)        if !contains(letters, firstLetter) {            letters.append(firstLetter)        }    }    for letter in letters {        var wordsForLetter = [String]()        for word in words {            let firstLetter = Character(word.substringToIndex(                advance(word.startIndex, 1)).uppercaseString)            if firstLetter == letter {                wordsForLetter.append(word)            }        }        result.append((letter, wordsForLetter))    }    return result}// [("C", ["Cat", "Chicken"]), ("F", ["Fish"]), ("D", ["Dog"]), ("M", ["Mouse", "Monkey"]), ("P", ["Pig"])]println(buildIndex(words))

??使用函數式編程,可以將代碼改寫為如下所示的樣子。

import Foundationlet words = [    "Cat", "Chicken", "Fish", "Dog", "Mouse",  "Pig", "Monkey"]typealias Entry = (Character, [String])func distinct<T: Equatable>(source: [T]) -> [T] {    var unique = [T]()    for item in source {        if !contains(unique, item) {            unique.append(item)        }    }    return unique}func buildIndex(words: [String]) -> [Entry] {    func firstLetter(str: String) -> Character {        return Character(str.substringToIndex(advance(str.startIndex, 1)).uppercaseString)    }    return distinct(words.map(firstLetter)).map {        (letter) -> Entry in return (letter, words.filter {                firstLetter($0) == letter        })    }}println(buildIndex(words))
柯裡化(currying)

??要理解柯裡化,我們先看看下面的例子。

import Foundationlet data = "5,7;3,4;55,6"// ["5,7", "3,4", "55,6"]println(data.componentsSeparatedByString(";"))// ["5", "7;3", "4;55", "6"]println(data.componentsSeparatedByString(","))

??在上面的例子中,我們使用字串的componentsSeparatedByString()方法根據指定的字元(串)將字串拆分成字串的數組。有些時候,我們可能需要用指定的字元(串)反覆的對出現的字串進行拆分,於是我們可以做出這樣的處理,如下所示。

import Foundationlet data = "5,7;3,4;55,6"func createSplitter(separator: String) -> (String -> [String]) {    func split(source: String) -> [String] {        return source.componentsSeparatedByString(separator)    }    return split}let commaSplitter = createSplitter(",")// ["5", "7;3", "4;55", "6"]println(commaSplitter(data))// ["5,7", "3,4", "55,6"]let semiColonSplitter = createSplitter(";")println(semiColonSplitter(data))

??明顯,按照上面的做法,我們可以重複的使用兩種拆分器commaSplitter和semiColonSplitter對字串進行拆分,而不用每次調用字串的拆分函數並指定拆分字元(串)。這種編程理念通常稱之為"部分化應用"(partial application),其原理是將函數中的一個或多個參數先固定下來,建立出一個新的函數。我們繼續往下看。

import Foundationlet data = "5,7;3,4;55,6"func createSplitter(separator: String)(source: String) -> [String] {    return source.componentsSeparatedByString(separator)}let commaSplitter = createSplitter(",")// ["5", "7;3", "4;55", "6"]println(commaSplitter(source: data))// ["5,7", "3,4", "55,6"]let semiColonSplitter = createSplitter(";")println(semiColonSplitter(source: data))

??這樣看起來不是更加優雅嗎?先傳入一個參數,稍後再傳入另一個參數來實現完整的功能,這其實就是所謂的函數的柯裡化。讓我們再來看一個例子吧。

func add(one: Int, two: Int, three: Int) -> Int {    return one + two + three}let sum = add(1, 2, 3)println(sum)

??我們也可以這樣來寫改寫add()函數。

func add(one: Int)(two: Int)(three: Int) -> Int {    return one + two + three}let step1 = add(1)let step2 = step1(two: 2)let step3 = step2(three: 3)println(step3)

??再來看一個例子,實現一個柯裡化的字串填充函數。

func stringPadding(startIndex: Int, paddingString: String)(source: String, length: Int) -> String {    return source.stringByPaddingToLength(length, withString: paddingString, startingAtIndex: startIndex)}let text = "Swift"let dottedPadding = stringPadding(0, ".")let paddingText = dottedPadding(source: text, length: 10)println(paddingText)    // Swift.....

[連載]Swift開發入門(06)--- 函數式編程

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.