Re: [心得] multi-method/dispatch
※ 引述《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
12/22 15:35, 2F
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 2 之 2 篇):
Ruby 近期熱門文章
PTT數位生活區 即時熱門文章