星期三, 7月 27, 2011

currying

前兩天看Wikipedia上的Currying時,突然頓悟了(可見之前都沒認真)。這個不看英文Wikipedia的說明,還真的是不容易看懂。簡單的說,假設有個 function 是 f( x, y, z ),currying 就是令 f1=f(1),當呼叫 f1( 2, 3 ) 時,就等於是呼叫 f( 1, 2, 3 )。



目前想到可以應用在 c/c# 沒辦法帶預設參數的情況上,像:
void func( int x, int y, int z ) { }
void new_func1( int y, int z ) { return func( 1, y, z ); }
void new_func2( int y, int z ) { return func( 2, y, z ); }

你可以想像到,這是一項複製、貼上的體力活,用 currying 的話,可以很快創造出新函數:
/* 以下為虛擬碼 */
void func( int x, int y, int z ) { }
new_func1 = func(1); /* new_func1 仍是函數 */
new_func2 = func(2); /* new_func2 仍是函數 */

在 python 裡,透過 *arg、**kwargs 可以很容易實現,文章可以參考 Currying and Python, a practical example,裏面有點複雜,其實只要看 curry 類別的部份,下面就是直接摘錄出來的實例:
class curry:
  def __init__(self, fun, *args, **kwargs):
    self.fun = fun
    self.pending = args[:]
    self.kwargs = kwargs.copy()

  def __call__(self, *args, **kwargs):
    if kwargs and self.kwargs:
      kw = self.kwargs.copy()
      kw.update(kwargs)
    else:
      kw = kwargs or self.kwargs
    return self.fun(*(self.pending + args), **kw)

def func( a, b, c ):
    print( a, b, c )
    return (a+b+c)

func1 = curry( func, 1, 100 )
func2 = curry( func, 2 )

print( func1( 200 ), func2( 300, 400 ) )

原理就是利用類別的特殊方法 __call__ ,呼叫 func2=curry( func, 2 ) 時,實際上是得到 curry 類別的實體。curry 類別的 __init__ 裡去做參數的判斷跟預存,等到把 func2 當函數執行時,就會執行到 __call__,這裡再去呼叫真正的函數。

沒有留言: