Swift07/90Days - 柯里化

Swift 中的柯里化

新手上路的學習筆記,若有錯誤還望指出,不勝感激。javascript

上集:理論預備

在學習柯里化的過程當中接觸到了三個有趣的概念,在此和各位分享一下。html

偏函數 (Partial Function)

偏函數是隻對函數定義域的一個子集進行定義的函數,是一個數學概念。java

偏函數定義以下:ios

從輸入值集合 X 到可能的輸出值集合 Y 的函數 f (記做f:X→Y) 是 X 和 Y 的關係,若 f 知足多個輸入能夠映射到一個輸出,但一個輸入不能映射到多個輸出,則爲偏函數。編程

換句話說,定義域 X 中可能存在某些值,在值域 Y 中沒有對應的值。從定義來看,偏函數是函數的超集。也就是說,函數都是偏函數,但偏函數不都是函數。swift

偏函數應用 (Partial Application || Partial Function Application)

上面說的概念是數學中的概念,和咱們將要接觸的內容無關。和咱們關係比較大的是偏函數應用 (Partial Application)。app

偏函數應用的定義以下:ide

在計算機科學領域,偏函數應用是指經過固定原函數的一部分參數生成新函數的過程,新函數的參數數目少於原函數。函數

經過數學公式演示一下,好比原函數 f 有三個參數: f:(X×Y×Z)→N ,經過綁定第一個參數 X ,咱們能夠獲得一個新的函數: partial(f):(Y×Z)→N學習

編程中的偏函數,好比廖雪峯老師在偏函數中說起的 functools 模塊的用法,其實就是偏函數應用,又叫局部函數應用,有道詞典翻譯爲切一刀在此表過不提。

爲避免混淆,下文中說起的偏函數均爲編程中的偏函數應用 (Partial Application)。

偏函數有點像是給函數的參數設置默認值同樣,不過偏函數更加靈活。好比下面這段 c 語言的代碼中,foo23 函數就是 foo 函數的偏函數,參數 b 的值被綁定爲 23

int foo(int a, int b, int c) {
  return a + b + c;
}

int foo23(int a, int c) {
  return foo(a, 23, c);
}

柯里化

柯里化和偏函數有些關係,可是是兩個徹底不一樣的概念。

柯里化定義以下:

柯里化(英語:Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。

經過數學公式來簡單的演示一下,原始的函數是一個普通的函數:f:(X×Y) → Z ,柯里化以後函數變成了:curry(f):X → (Y→Z)

咱們用幾段 js 代碼演示一下柯里化的過程。首先先看一個普通的函數 foo ,返回參數的平方:

var foo = function(a) {
    return a * a;
}

假設咱們的函數都只能有一個參數,那麼能夠用下面的方式模擬出一個多參數函數:

var foo = function(a) {
    return function(b) {
        return a * a + b * b;
    }
}

咱們能夠這樣調用 foo(3)(4)

柯里化解決的問題是:將多參數函數轉變爲一系列單參數函數的鏈式調用。

柯里化背後的基本想法是函數能夠局部應用,意思是一些參數值能夠在函數調用以前被指定,而且返回一個新的函數。也就是偏函數的基本思想。

小結

能夠看到,柯里化將函數進行了~~肢解~~拆分,這樣咱們能夠很容易的實現偏函數。好比:

var foo = function(a) {
    return function(b) {
        return a * a + b * b;
    }
}
var foo3 = foo(3);
foo3(4);

這就出來了。函數 foo3 就是 foo 函數的偏函數。

簡單總結一下偏函數和柯里化以及二者的關係:

  • 偏函數應用:固定原函數的幾個參數值,從而獲得一個新的函數。
  • 函數柯里化:一種使用匿名單參數函數來實現多參數函數的方法。
  • 關係:柯里化可以輕鬆的實現某些偏函數應用。

中集:柯里化與偏函數

前面扯了不少有的沒的,接下來咱們來看看在 Swift 中柯里化。

節目預告:下面的例子極爲簡單,讓各位見笑了。但願經過這些最簡單的例子演示柯里化最關鍵的部分。

傳統手段:老老實實傳參數

咱們先來寫個最簡單的東西好了:計數器,每次運算後+1便可:

func add(a:Int) -> Int{
    return a + 1;
}

var a = 0

a = add(a)   // 1
a = add(a)   // 2

可是每次都+1,步子有點小了。有時咱們可能也須要+2,+3,+4,那麼簡單,咱們把須要加的步長放在參數裏就行:

func add(a:Int, b:Int) -> Int{
    return a + b ;
}

var a = 0

a = add(a, 1)   // 1
a = add(a, 2)   // 3

彷佛沒什麼問題。那麼問題來了:我大部分時間都只要+1啊,我可能只是100次調用有90次要+1,爲了剩下的那10次,每行代碼都要多個參數,是否是麻煩了點?OK嫌麻煩咱們能夠經過設置默認值的方式解決。

進階手段:能夠設置默認值

在函數定義的時候咱們就給它定好默認值是1,那不就得了:

func add(a:Int, b:Int=1) -> Int{
    return a + b ;
}

var a = 0

a = add(a)   // 1
a = add(a, b:2)   // 3

若是你要+1,行,你別寫參數我給你調好了自動+1;若是你要+2+3,行,愛加幾加幾,本身寫到參數裏去。

彷佛沒什麼問題。那麼問題來了:我有一半的時間要+1,有一半的時間要+2,還有些時間+3+4,怎麼玩?

唉可真是磨人的小妖精。

柯學手段:經過柯里化實現

和前面的例子不同的是,柯里化的函數返回一個新的函數而不是計算後的結果。計算後的結果要經過返回的新函數計算得到:

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

let addOne = add(1)
let addTwo = add(2)

var a = 0

a = addOne(a: a)    // 1
a = addTwo(a: a)    // 3

在這個例子裏,咱們生成兩個新的函數:addOneaddTwo 分別進行+1和+2操做。能夠看到,經過柯里化實現偏函數是十分方便的,一切都順其天然,水到渠成。

經過這個例子能夠看出,柯里化是偏函數的方法論,偏函數是柯里化背後的~~男人~~思想。

下集:實例方法與柯里化

實際上,Swift 中的實例方法也是柯里化方法,你能夠傳個實例做爲第一個參數。

咱們還就和計數器槓上了,再次以計數器爲例:

class Counter {
    var b: Int = 1

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

咱們能夠初始化實例對象而後來調用:

let counter = Counter()
var a = counter.add(1)  // 2

咱們也能夠這樣作:

let add = Counter.add   // Function
let counter = Counter()
var a = add(counter)(1) // 2

這兩個是徹底等價的。

有點神奇啊,不是嗎?注意, let add = Counter.add 這個定義後面沒有小括號。也就是說,咱們並無調用它,只是指向它,像一個指針同樣指了過去。

咱們能夠按住 option 鍵查看一下 add 的類型:let add: Counter -> (Int) -> Int 。它的參數是一個 Counter 類型的實例,返回的是另外一個新函數,這個新函數的參數類型是 Int ,返回的類型也是 Int ,和類裏定義的函數類型是同樣的。

看起來好像挺有趣的,不過問題來了:有啥用呢?

這個問題我還沒辦法回答,由於我也是剛剛接觸這部份內容。我只能作一名知識的搬運工了。

Instance Methods are Curried Functions in Swift 這篇文章裏,做者舉了一個例子:用 Target-Action 模式實現一個 Control

protocol TargetAction {
    func performAction()
}

struct TargetActionWrapper<T: AnyObject> : TargetAction {
    weak var target: T?
    let action: (T) -> () -> ()

    func performAction() -> () {
        if let t = target {
            action(t)()
        }
    }
}

enum ControlEvent {
    case TouchUpInside
    case ValueChanged
    // ...
}

class Control {
    var actions = [ControlEvent: TargetAction]()

    func setTarget<T: AnyObject>(target: T, action: (T) -> () -> (), controlEvent: ControlEvent) {
        actions[controlEvent] = TargetActionWrapper(target: target, action: action)
    }

    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }

    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    }
}

用法和平時沒什麼兩樣:

class MyViewController {
    let button = Control()

    func viewDidLoad() {
        button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
    }

    func onButtonTap() {
        println("Button was tapped")
    }

下集的下集:其餘

因爲沒有在實戰中用過,暫時不對柯里化作太多評價。就目前的瞭解來看,它可讓咱們很方便的對函數進行局部調用,讓代碼更加靈活,語意更加清晰。

若是想感覺一下原汁原味的柯里化,不妨接觸一下 Haskell 這門語言。 Haskell 中多參數函數都是經過柯里化實現的。它的做者是 Haskell Brooks Curry,是的就是柯里化 (Curring) 的命名者 (不過並非他發明的)。

使用 Mac 的朋友能夠下載 Haskell Platform for Mac ,而後經過 ghci 命令試一試 Haskell 的代碼。

固然也有反柯里化,感興趣的同窗能夠繼續瞭解一下:)


References

相關文章
相關標籤/搜索