From: Robert Klemme Date: 2004-05-28T22:00:23+09:00 Subject: Re: Visitor Pattern in Ruby "Charles Comstock" schrieb im Newsbeitrag news:Pine.LNX.4.44.0405261755410.17280-100000@earwig.int.cec.wustl.edu... > On Wed, 26 May 2004, Robert Klemme wrote: > > [cut] > > > > The only problem I can see at the moment is with classes that are not in the > > global scope: > > > > >> module Foo; class Bar; end; end > > => nil > > >> Foo::Bar > > => Foo::Bar > > >> Foo::Bar.to_s > > => "Foo::Bar" > > > > This string is likely to make problems in a method name: > > > > >> class X > > >> def visit_Foo::Bar > > >> end > > >> end > > NameError: undefined local variable or method `visit_Foo' for X:Class > > from (irb):5 > > > > > > Maybe you just map "::" to "_" or something similar. > > > > Shoot I completely forgot about that problem. Hmm that causes all > sorts of problems. I can't simply do a gsub(/::/,'_') because then > what would happen if you wrote: > > class A::B > end > > and > > class A_B > end > > They would be indistinguishable. I suppose you could just take that > into account and do something like split(/::/)[-1] and just assume you > don't have a naming collision. I would think that generally you would > be only interested in the class of the most immediate scope, but it > certainly allows for some ambiguity. > > Speaking of which what is up with this: > irb(main):115:0> "a::b::c".intern > => :"a::b::c" > > Is that syntax documented? That might work in your case. Hm... > > An alternative approach could be to use the visitor to get a proc for the > > current instance, like in > > > > class Visitor > > def visit(obj) > > bl = nil > > > > obj.class.ancestors.each do |cl| > > bl ||= visitors[cl] # hash lookup > > end > > > > bl.call( obj ) if bl > > end > > end > > > > Something like that would certainly work it just doesn't seem as nice a way to > define each visitor. Hmm I shall have to think on this. Personally I'd prefer the mapping approach - it seems to be more OO to me. But then, a maybe a more general approach would be in order: #!/usr/bin/ruby class MultipleDispatchMethod def initialize(arity) raise ArgumentError, "type count must be > 0" unless arity > 0 @arity = arity @procs = {} end def arity; @arity; end def define_dispatch(*types, &method) raise ArgumentError, "Wrong number of types" unless types.size == arity # allow for prototype based definition types.map! {|tp| cl = tp.class; cl == Class || cl == Module ? tp : cl} @procs[ types ] = method end def call(*args) types = args.slice( 0, arity ).map{|c| c.class} @procs[ types ].call( args ) end end # define method FooBar FooBar = MultipleDispatchMethod.new( 2 ) FooBar.define_dispatch( String, Fixnum ) do |s,n,str| ( s + str ) * n end FooBar.define_dispatch( "hello", 5.3 ) do |s,f,str| FooBar.call( s, f.to_i, str ) end p FooBar.call( "a", 10, "b" ) p FooBar.call( "a", 10.45, "b" ) Of course the lookup could be made much smarter, so it tries super class coombinations as well. But that would be far more costly, which is maybe not such a good idea for a method call. Kind regards robert