Re: [心得] multi-method/dispatch

看板Ruby作者 (godfat 真常)時間18年前 (2006/12/21 22:57), 編輯推噓0(002)
留言2則, 2人參與, 最新討論串2/2 (看更多)
※ 引述《godfat (godfat 真常)》之銘言: : 簡單地說,當我們說:「unit.walk_to(dist)」時,是否可以獲得 unit 的真實 : 型別?那麼,何不故技重施,以便獲得第二個真實型別? : 避免寫太長不好閱讀,待續… 原本的程式碼是 C++ 的,這裡我以 Ruby 做示範: class A def go rhs rhs.goA self # 知道左邊是 A 了!右邊是誰? # 倒轉過來呼叫就知道右邊是誰了 end def goA rhs # 上面假設 rhs 也是 A, 那麼倒轉呼叫後就會到此 # 於是我們知道,左右都是 A puts 'AA' end def goB rhs; puts 'BA'; end def goC rhs; puts 'CA'; end end class B def go rhs; rhs.goB self; end def goA rhs; puts 'AB'; end def goB rhs; puts 'BB'; end def goC rhs; puts 'CB'; end end class C def go rhs; rhs.goC self; end def goA rhs; puts 'AC'; end def goB rhs; puts 'BC'; end def goC rhs; puts 'CC'; end end a, b, c = A.new, B.new, C.new a.go b # AB b.go c # BC c.go a # CA a.go c # AC 不過其實缺點還滿多的,一、程式寫法不是那麼地直覺 二、呼叫順序變得很重要,難以將 a.go b 和 b.go a 變成等價 三、如果要支援更多的 invoker, 會變得很困難且繁瑣 summary multi-method/dispatch 的三種實作法 1. 暴力搜尋 缺:1. error-prone 2. 難以支援二種以上的型別 優:1. 執行效率良好 2. 實作方式簡單 2. 先註冊,後用 map/hash 搜尋 (Ruby lib multi 的實作法) 缺:1. 需要額外資源 2. 執行效率差 優:1. 彈性佳、容易擴充至支援任意多種型別 2. 操作方式簡單 3. 翻轉搜尋 缺:1. 彈性非常差 2. 難以支援二種以上的型別 優:1. 執行效率良好 2. 操作方式簡單 個人覺得,第一種做法最直覺,第二種做法最好,第三種做法最有趣 XD 三種方法都大致看完後,就來看 http://rubyforge.org/projects/multi/ 用起來感覺如何了 (p.s. 據說 Common Lisp Object System 直接支援 multi-method/dispatch) 先來看範例,取自裡面的 multi_example.rb require 'pp' require 'multi' require 'smulti' # btw, 順便問一下 XD 為何如果我不先 require 'rubygems' 的話, # multi 等用 gem 安裝的 lib 會讀不到啊?rubygems 的設定翻半天, # 還是搞不清楚是怎麼回事|||b class Foo def initialize multi(:hiya, 0) {|x| puts "Zero: #{x}" } multi(:hiya, Integer) {|x| puts "Int: #{x}" } multi(:hiya, String) {|x| puts "Str: #{x}" } multi(:hiya, lambda {|x| x.size > 2 }) {|x| puts "GT2: #{x}"} end end f = Foo.new() f.hiya(0) # Zero: 0 f.hiya(5) # Int: 5 f.hiya("hello") # Str: hello f.hiya([1, 2, 3]) # GT2: 123 begin f.hiya([1]) rescue puts $! # No match for #<Foo:0x7fec852c>.hiya([1]) end multi(:fac, 0) { 1 } multi(:fac, Integer) {|x| x * fac(x-1)} puts fac(5) # 120 multi(:reverse, []) { [] } multi(:reverse, Array) {|list| [list.pop] + reverse(list) } pp reverse([1,2,3]) # [3, 2, 1] multi(:baz, 3, Object) { puts 3 } multi(:baz, Object, String) {|o, str| puts str } baz(3, "three") # 3 baz(2, "two") # two multi(:retest, /^a(.*)/) {|x| x } multi(:retest, String) { '' } pp retest('foo') # "" pp retest('afoo') # "afoo" smulti(:foo, 'a') { puts "a FOUND" } smulti(:foo, /./) {|s, rest| foo(rest) } smulti(:foo, // ) { puts "a NOT FOUND" } foo('a') # a FOUND foo('') # a NOT FOUND foo('ab') # a FOUND foo('ba') # a FOUND foo('bb') # a NOT FOUND # 以下是我額外的測試,結果和上面的 a FOUND 系列完全相同 multi(:boo, /.*a+.*/) { puts 'a FOUND' } multi(:boo, String) { puts 'a NOT FOUND' } boo('a') boo('') boo('ab') boo('ba') boo('bb') 也就是說,這個 multi 其實該有的都有了,甚至 class 和 instance 可以 混著使用。另外需要注意的是越先定義的 method 有越高的優先權, 如果寫 multi(:coo, Integer){} multi(:coo, 0){} 則下面的 method 永遠不會被 invoke, 因為 0 屬於 Integer... 這種時候,0 一定要優先定義 我個人認為這也許算是個缺點,有空會看看能不能修正這個問題 另外還有一個我覺得很大的問題是:當引數完全符合,但有多餘參數剩下時, 仍然算是 match, 參照以下: multi(:test, Integer, String, Integer){puts 'match'} test(1, 'XD') 1 符合 Integer, 'XD' 符合 String, 欠 Integer, 但依然算是 match 我不知道作者是不是故意這樣做的,還是單純只是一個 bug 除此之外,其實還欠缺對稱性的問題,如 a.go b 和 b.go a 是否相同? 為此,我對該 lib 稍作了一點暫時性的擴充,使得: multi_unordered(:combine, A, B){ puts 'AB' } a, b = A.new, B.new combine(b, a) # AB combine(a, b) # AB 欲知詳情,請待下回分曉 -- 「行け!Loki!」(rocky ロッキー) -Gurumin ぐるみん 王子? XD -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 61.217.103.191

12/22 00:02, , 1F
太累了,接下來的改天再寫...
12/22 00:02, 1F

12/22 15:35, , 2F
因為 rubygems 會 override require , 等同 require_gem
12/22 15:35, 2F
文章代碼(AID): #15Yg3IqQ (Ruby)
討論串 (同標題文章)
文章代碼(AID): #15Yg3IqQ (Ruby)