From: "John W. Long" Date: 2005-02-10T14:37:32+09:00 Subject: Building a Better Functor Hi, I've been messing around a with functors lately (see [1] and [2]) and have found them really helpful for testing. They make great light-weight MockObjects. Consider the following example: Pretend I have a method called redirect that I am trying to test: def redirect(url, sys=Kernel) # # code here that writes out an HTTP header # that causes the redirect to url # # my header has been written so kill the script: sys.exit end Given the following definition for a functor I can easily mock out the sys.exit call: class Functor def initialize(method, &block) @method = method @block = block end def method_missing(symbol, *args) if @method.to_s.intern == symbol @block.call(*args) else super end end end Here's my test method: def test_redirect__exit exited = false sys = Functor.new do |meth, *args| exited = true if meth == :exit end redirect('test/url.html', sys) assert(exited) end As you can see a functor is a pretty amazing little object. You can define method_missing a couple of different ways [3], but you are probably having your own ideas by now. Also note: because I'm using blocks my functor methods have access to variables that are in scope where the functor is defined (like "exited" in this case). As I have used functors I have often found that there are times when it would be nice for a functor to support multiple methods--not just one. I've implemented seen this implemented a couple of ways, but haven't been totally satisfied with the results. Today, while talking to a colleague I hit upon an idea: why not allow functors to be added together to allow the construction of larger functor objects. Consider the following definition for a functor: class Functor def initialize(method = nil, &block) @methods = {} add_method(method, &block) if block_given? end def __add__(f) n = self.class.new n.instance_variable_get( '@methods' ).update( @methods ).update( f.instance_variable_get('@methods') ) n end def add_method(method, &block) @methods[method.to_s.intern] = block end def method_missing(symbol, *args) case when @methods.has_key?(symbol) @methods[symbol].call(*args) when symbol == :+ __add__(*args) else super end end end With an additional top level method: def functor(method, &block) Functor.new(method, &block) end This will allow you to do the following: f = functor(:run) { puts 'running...' } + functor(:jump) { puts 'jumping...' } + functor(:play) { puts 'playing...' } f.run #=> "running..." f.jump #=> "jumping..." f.play #=> "playing..." This is a seriously cool concept. I'd like to see a version of this included in the standard lib. Perhaps even added to the core by modifying proc to behave the same manner (see Nowake's proposal[1]). Does anyone else have a better implementation? Can you see ways of improving my own? I've uploaded my tests and source code on my Web site [4]. -- [1] http://groups-beta.google.com/group/comp.lang.ruby/browse_thread/thread/491add60f8da4d0f [2] http://rubyforge.org/pipermail/chicagogroup-members-list/2004-October/thread.html [3] http://rubyforge.org/pipermail/chicagogroup-members-list/2004-October/000024.html [4] http://johnwlong.com/downloads/functor/ -- John Long http://wiseheartdesign.com