From: Charles Comstock Date: 2004-05-28T22:01:14+09:00 Subject: Re: Visitor Pattern in Ruby On Thu, 27 May 2004, Robert Klemme wrote: > > "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. > If you don't check the superclass then the usefulness of the visitor pattern drops significantly. The whole idea is to be able to walk a hiearchy and catch different classes at different levels. I'm not trying to do multiple dispatch, just double dispatch, so I only really need to walk the type hiearchy in one dimension. That's not great for efficiency, but it's not horrible either. The main problem with the proc based solution is I no longer can leverage inheritance on the visitor side, only on the visitee side. In other words I can't create a skeleton of a visitor and derive from it to add specific functionality, unless I implement my own system of inheritence of the procs. Which is slow and bad, and not really a solution. While I can understand it wouldn't work effectively for cases where you were walking one hiearchy that was inside a module and one that was outside with similar names, I think the solution I presented works fine for 90% of the use cases. Charlie