新手上路的學習筆記,若有錯誤還望指出,不勝感激。javascript
在學習柯里化的過程當中接觸到了三個有趣的概念,在此和各位分享一下。html
偏函數是隻對函數定義域的一個子集進行定義的函數,是一個數學概念。java
偏函數定義以下:ios
從輸入值集合 X 到可能的輸出值集合 Y 的函數 f (記做f:X→Y) 是 X 和 Y 的關係,若 f 知足多個輸入能夠映射到一個輸出,但一個輸入不能映射到多個輸出,則爲偏函數。編程
換句話說,定義域 X
中可能存在某些值,在值域 Y
中沒有對應的值。從定義來看,偏函數是函數的超集。也就是說,函數都是偏函數,但偏函數不都是函數。swift
上面說的概念是數學中的概念,和咱們將要接觸的內容無關。和咱們關係比較大的是偏函數應用 (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
在這個例子裏,咱們生成兩個新的函數:addOne
和 addTwo
分別進行+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 的代碼。
固然也有反柯里化,感興趣的同窗能夠繼續瞭解一下:)