fletter

«

»

One of the handy things about Lisp that I've gotten attached to is the flet function. Using it you can temporarily redefine a function but have its definition revert back to the original when its body exits. This is analagous to the way a block in Ruby can have some specific behaviour that goes away when it exits, but flet doesn't seem to have a Ruby equivalent. (The best my searches found was pstickne enlightening folks in #ruby-lang about this lisptastic functionality.) Anyway, I just figured out an implementation.

 def Object.flet(bindings,  &block)
 old_methods = {}

bindings.each do |the_method,  body|
 old_methods[the_method] = method(the_method)
 define_method(the_method,  body)
 end
 
 begin
 block.call
 ensure
 bindings.each do |the_method,  body|
 define_method(the_method) { |*args| old_methods[the_method].call(*args) }
 end
 end
 end
 

This lets you do stuff like:

 puts "foo"
 
 Object.flet(: puts => λ { |str| print "#{str.reverse}
 " }) do
 puts "foo"
 end
 
 puts "foo"
 

... which will output:

foo
 oof
 foo

Pretty cool, huh? I haven't done much funky metaprogramming in Ruby, so it's quite possible my attempts to Lispify it here are dangerous and/or misguided. (That's what comments are for, BTW.) But it's coming in pretty handy for me so far.

Update: Josh points out that this is not thread-safe and thoroughly violates encapsulation in a way that should earn me a severe thrashing (dare I say—flogging?) were I to use it in production in anything but the simplest of circumstances. In my mind it's kind of like using eval: all advice concerning actually using it goes along the lines of "never do this". Only once you learn when it's OK to ignore such strong warnings can you be trusted to posess the necessary discretion to use it in practice.

The context here is that I want to run a bunch of tests and gather failure data within my library. But when I require test/unit, it sets up an at_exit block that actually initiates the running of the tests as a final act before the execution finishes. This is redundant since I already initiated them myself in the library. But once you've created an at_exit, there's no mechanism for disabling it. I could just redefine at_exit permanently, but it's quite possible I'd want to use it elsewhere. flet allows me to limit the scope of this change. But it's only forgivable because I'm not really working with an object-oriented phenomenon here; I'm working with a lower-level feature of Ruby.

 Object.flet(: at_exit => lambda {}) do
 # keep test/unit's at_exit block from running
 require 'test/unit'
 end
 

And as Josh notes, Rubinius will make stuff like this much easier. I imagine I wouldn't even have to resort to flet to get around my at_exit problem.

« older | 2007-10-13T07:00:01Z | newer »