2006/08/12

小試 FP in Ruby

def mem_fun(mf)
  lambda {|obj, *args| obj.method(mf.to_s).call(*args)}
end

給予此函式一個函式名稱字串或 Symbol,就可以把 member function call 的形式轉換為 plain function call(當然,Ruby 所謂的 plain function 其實也是 member function)。不過相較於 C++ 的 member function adaptor,Ruby 是在執行期以 reflection 喚起那個函式,較為動態但間接層數較多,而不像 C++ 經最佳化後效能幾乎無損(也比較僵硬)。舉個使用例子:

plus = mem_fun(:+)
plus.call("abc", "def")  # --> "abcdef"

lambda 所接收的 block 參數列內的 *args 功能相當於 C++ 或 Java 1.5 的 "..." 參數,傳入的剩餘引數會被打包成一個 array(即 args),因為 Ruby array 是異質(heterogenous)容器,這個設計運作良好。而 block 主體內的 *args 則反其道而行,將 args array 打散為個別引數。lambda 本身的作用在上一篇文章已經提過,是將其後隨附的 block 轉換為可呼叫的 Proc object。因此整體效果是將 mem_fun(:mf).call(obj, arg1, arg2, arg3, other arguments) 轉換為 obj.mf(arg1, arg2, arg3, other arguments)

接著試寫 bind,將一個 Proc object 的某些引數綁定為特定變數或固定值。例如:

bind(plus, :_1, "def").call("abc")  # --> "abcdef"
bind(plus, "def", :_1).call("abc")  # --> "defabc"
bind(plus, :_2, :_1).call("abc", "def")  # --> "defabc"

在那之前,我們先寫 adapt,把傳遞給 p 的引數全部先經另一個 unary_p 處理後才真正傳入。

def adapt(p, unary_proc)
  lambda do |*args|
    p.call(*args.collect {|x| unary_proc.call(x)})
  end
end

接著 bind 就很好寫了:

def bind(p, *a)
  lambda do |*args|
    adapt(p, lambda do |x|
               x.class == Symbol && x.to_s =~ /_(\d+)/ ?
                 args[$1.to_i - 1] : x
             end).call(*a)
  end
end

接著我們可以試寫函式合成(function composition)。或許風格不佳,我把語法設計為 f + g,如此造出的 function object 將先喚起 f,然後把結果再傳入 g+ 運算子必須定義為 class Proc 的 member,而 Ruby 正允許我們這麼做:

class Proc  # reopen class Proc
  def + (unary_p)
    lambda {|*args| unary_p.call(self.call(*args))}
  end
end

用法如:

g = plus + lambda {|x| puts x}
g.call("abc", "def")  # prints "abcdef"

當然,以上程式碼全部都是「泛型」(generic)的 ─ Ruby 是個動態型別語言,靜態型別語言所講的「泛型」對它沒有意義。

不過,實用上似乎不太需要這些 higher-order functions,因為 Ruby 的 block 機制本身就相當好用了。而且效率也是問題,每次經過一個 higher-order function,就得負擔 *args 的打包、拆散成本,closures 的時間、空間花費,以及 Ruby 本身的函式呼叫也算昂貴。因此寫這些函式純粹只是好玩而已 XD。

--
好想要 C++ lambda expressions XD。

Anonymous Anonymous8/15/2006 10:36 am 說:

講到泛型, 目前還是覺得 Haskell 那種較純
道地的 generic + constraint

但是很抽象就是了, Monads 遊客到現在還搞
不懂

 
Blogger Josh Ko8/15/2006 12:54 pm 說:

嗯,的確,目前「感覺上」泛型和 functional programming 血緣關係比較近(所以「同步率」較高 XD)。為了把「感覺上」三字去掉(或換成較具確定性的詞彙) ─ 輔修數學系(17 日放榜)!

雖然僅只接觸很淺顯的範例,Haskell(and pure functional programming)感覺上真的很有趣呢 :)。

 
Anonymous Anonymous8/15/2006 1:16 pm 說:

不過 Haskell 是 static typing 喔 ^^

只是它有 type inference, 而且在真正
需要 constraint 的場合中,「感覺上」說
來跟跟靜態語言也不是差很多!

不過這是了解不深的說法, 呵呵, 有待繼續
學習~

 

<< 回到主頁