作者godfat (godfat 真常)
看板Ruby
标题Re: [心得] multi-method/dispatch
时间Thu Dec 21 23:49:41 2006
※ 引述《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
1F:→ godfat:太累了,接下来的改天再写... 12/22 00:02
2F:→ yjc1:因为 rubygems 会 override require , 等同 require_gem 12/22 15:35