2016/8/29

Currying 柯里化 in Scala


在 functional programming 中,Currying 是由 Moses Schönfinkel and Gottlob Frege 發明,並以 Haskell Brooks Curry 來命名,核心概念是將一個多個參數的函數轉換成只接受一個參數的函數,並回傳一個接受剩下的參數的新函數 (the other arguments having been specified by the curry)。


Currying 的優點在於,可以將所有的函數,都轉換成只接受一個參數,並回傳一個值的固定形式,也就是用多個單一參數的函數,組合成多參數的函數。


Currying


add 是一個一般的 function,有兩個參數,所以呼叫時必須直接傳入兩個參數 add(1,2),而 add2 是 currying 後的 function,可以分成兩個部分來看,(y:Int) => x+y 是一個包含了 closure 變數 x 的 匿名函數,再以一個參數 x 的 function 封裝這個匿名函數。


scala> def add(x:Int, y:Int) = x+y
add: (x: Int, y: Int)Int

scala> def add2(x:Int) = (y:Int) => x + y
add2: (x: Int)Int => Int

scala> add(1,2)
res14: Int = 3

scala> add2(1)(2)
res16: Int = 3

然後就可以分兩段呼叫 add2,但呼叫時必須依照參數的順序,不能先指定 y 的值。


scala> add2(1)
res17: Int => Int = <function1>

scala> add2(1)(2)
res18: Int = 3

如果要直接使用 (y:Int) => x +y 這個匿名函數,前面必須要先能定義 x 得值,當無法先知道 x 的數值時,我們就以一個新的函數,加上參數 x,再後面使用這個新的函數,也就是剛剛 add2 的 currying 化的版本。


scala> val x=1
x: Int = 1

scala> val add5 = (y:Int) => x +y
add5: Int => Int = <function1>

scala> add5(2)
res25: Int = 3

Uncurrying


如果沒有 Currying 的功能,其實只用匿名函數也可以實現多參數函數。在 Scala 的 Function 中,提供了一個將 currying 化參數展開的 uncurried 方法。


展開後,就跟 add(x:Int, y:Int) 使用起來是一樣的。


scala> def add2(x:Int) = (y:Int) => x + y
add2: (x: Int)Int => Int

scala> val add3 = Function.uncurried(add2 _)
add3: (Int, Int) => Int = <function2>

scala> add3(1,2)
res15: Int = 3

Partial Function


Partial Function 跟 Currying 化的 function 看起來很類似,但實際上並不同,Partial Function 是將一個多參數的函數,給定其中幾個參數的值,然後能得到一個新的函數。


以剛剛的 add 為例,原本的 add 需要兩個整數參數 x 及 y,但我們可以先給定 y 的值,得到新的函數 add4,然後在後面使用 add4。


scala> val add4 = add(_:Int, 2)
add4: Int => Int = <function1>

scala> add4(1)
res21: Int = 3

這跟 currying 最大的差異是,把一個多參數的函數 currying 化的時候,必須要從第一個參數開始,一個一個拆解成單一參數的函數,但 Partial Function 能直接給定眾多參數中的某幾個參數,不需要依照參數的順序依序拆解。


Function Composition


functional programming 的概念最重要的是從數學的代數理論而來的,因為在進行代數運算時,通常都是一些方程式跟變數的推導,因此會產生出很多以函數來進行運算的概念。


舉個最簡單的例子來說,函數的基本定義如下,y是x的函數


y=f(x)

但如果有另一個函數 z,是 y 的函數,我們會寫成


z=g(y)

做個簡單的代換,y 可以直接換成 f(x),也就是說,z 可以經過兩次函數運算,得到結果


z=g(f(x))

以下兩篇文章,以一個 summation 的函數為例,說明如何以 scala 實作,functional programming 的用途最重要的是可以用最接近數學代數的方式,進行程式設計,不同於物件導向的出發點,是要以物件、繼承等等概念反應現實生活中的模型。


如何在Scala中實現合成函數


Scala中的Currying


就如同 Scala中的函數式特性 所說一段話:2003年,一個叫Martin Odersky 的醉漢看見了好時瑞森花生醬杯的廣告,展示了某個人的花生醬倒入另一個人的巧克力的場景,他忽然有了個點子,創造了Scala,一種結合了面向對象和函數式編程的語言。這同時激怒了兩個陣營的忠實信徒,他們立刻宣佈要發動聖戰燒死異教徒。


整合了兩者而誕生的 Scala 註定會一直存在著無解的爭議。


References


柯里化


scala Currying


Function Currying in Scala


柯裡化對函數式編程有何意義?


Swift 柯裡化(Currying)


柯裡化在scala中用途?


JS 柯里化(curry)


為什麼要柯裡化(why-curry-helps)