Archive for Metaprogramming

inherited, ObjectSpace, and Test::Unit

As a part of working on a submission for the homework assignment for Ruby magazine The Rubyist, I wrote a game server so my co-workers and I could test our bots against each other.

Part of what’s needed for a good server is the ability to automatically load classes and instantiate them. If you’re familiar with ObjectSpace and you have a way to identify the bot classes, it’s not too hard. The only thing that identifies a bot class is a move! instance method, but that should be enough:

player_classes = []
ObjectSpace.each_object(Class) do |klass|
  player_classes << klass if klass.instance_methods.include?('move!')
end
player_classes

It’s a little nicer in Ruby 1.9, because if you don’t pass a block to ObjectSpace.each_object, you get an Enumerable::Enumerator, so you can do something like this:

ObjectSpace.each_object(Class).select do |klass|
  klass.instance_methods.include?(:move!)
end

(Ruby 1.9 uses symbols for method names here instead of strings.)

Of course, if you knew that all the player classes would be subclasses of, say, TreasuredRuby::Bot, you could forgo using ObjectSpace altogether and use Class#inherited instead:

class TreasuredRuby::Bot
  PLAYER_CLASSES = []
  def inherited(klass)
    PLAYER_CLASSES << klass
  end
end

Where else are descendant classes treated specially? Test::Unit, specifically Test::Unit::TestCase subclasses. However, Test::Unit uses ObjectSpace to collect its descendant classes. There have been discussions about the problems it creates for JRuby, although JRuby handles ObjectSpace.each_object(Class) because of Test::Unit.

There was recently a patch suggested on the Ruby Core mailing list to replace using ObjectSpace with Class#inherited (and perhaps not the first), but it’s still unapplied. There are arguments against using Class#inherited: if you want to use Class#inherited in your TestCase classes, for example, you had better call super.

We’ll see if Test::Unit changes.

Comments

Evil: knowing when a method got passed the default value

Let’s say you’re crazy. Now, as a crazy person, you might have a method like this:

def greet_world(salutation="Hello")
  "#{salutation} World!"
end

You’re not so crazy that you can’t write working Ruby code. Anyway, it’s totally useful:

greet_world          # => "Hello World!"
greet_world('Hi')    # => "Hi World!"
greet_world('Yo')    # => "Yo World!"
greet_world('Hola')  # => "Hola World!"

But then you see someone do this:

greet_world('Hello')  # => "Hello World!"

And it drives you even crazier. Being totally crazy, you feel a compulsion to make fun of people who pass the default value for an argument of a method. But how in the world can you tell?

Well, it turns out (and you probably know this already) that the argument list of a method is a proper venue for executing code. You know this because you’ve seen this a thousand times: def something(time=Time.now). That Time.now happens when you call the method without that optional argument. It doesn’t happen when Ruby first parses the method and adds it to the method table for the class. That wouldn’t be too helpful.

So… you can execute arbitrary code in the method argument list. But what kind? What’s the scope? Well, anything you can do in a method body is fair game (and anything you can’t is not). You can’t define a class, for instance. And the scope? The method body itself.

You’re crazy, remember? But not so crazy that you don’t remember that every statement has a return value. Like assignment. Assignment returns a value. And you’ve got a place in the argument list that’s scoped with the method body. And you’ve got a deep desire to mock people who pass default values.

BOOM!

def greet_world(salutation=(salutation_not_given='Hello'))
  if salutation == 'Hello' and not salutation_not_given
    "YOU HAVE NO IMAGINATION!"
  else
    "#{salutation} World!"
  end
end

greet_world('Hello')  # => "YOU HAVE NO IMAGINATION!"
greet_world           # => "Hello World!"
greet_world('Hi')     # => "Hi World!"

salutation_not_given is nil if you’ve given a value. It’s one of those things.

ANYWAY. Use that at your own peril.

Comments (4)

A better try — chaining methods and nil

Of #try, #andand, and #do_or_do_not, the one I like best (and I don’t even like any of them that much, but that’s my next post) is #try.

What they are all basically for is chaining method calls when one of methods might return nil, resulting in a NoMethodError. Here’s Chris Wanstrath’s implementation:

class Object
  ##
  #   @person ? @person.name : nil
  # vs
  #   @person.try(:name)
  def try(method)
    send method if respond_to? method
  end
end

It’s almost right!

First of all, I’d argue that if #try is trying to be better than something, it had better be better than @person.name if @person. That looks and reads so nicely I can scarcely see why it needs replacing. @person.name if @person is so obviously better than @person ? @person.name : nil that I hope people are using that before they start thinking about monkey patching Object (which I hear is destroying Ruby). Anyway, I’d fix the documentation first.

But I think the biggest problem with #try is that it’s not doing what it says it’s doing. It’s not a replacement for @person ? @person.name : nil. That’s certainly not what the method is doing. Here’s what I’d do:

class Object
  ##
  #   @person.name if @person
  # vs
  #   @person.try(:name)
  def try(method)
    self.send(method) if self
  end
end

See? Much clearer; it does what it says. Using respond_to? means that the original #try is about something else; it’s about whether or not an object responds to a message. This #try is about sending a message to an object unless it’s nil or false, which is closer to what the original use case was… right?

But then it occurred to me that the issue is nil, not nil or false. The problem isn’t getting false in the middle of method chains, it’s nil. So maybe what #try should really be is this:

class Object
  ##
  #   @person.name unless @person.nil?
  # vs
  #   @person.try(:name)
  def try(method)
    self.send(method) unless self.nil?
  end
end

I might even use that.

Comments (9)

The least of them: minimum (and maximum) array values (plural!)

Every once in a while, I find myself wanting to do things like this:

017:0> %w(one two four six).find_all_min {|w| w.length}
["one", "two", "six"]
018:0> [3, 2, 1, 3, 1, 1, 3, 2].find_all_max
[3, 3, 3]

I want the values from an array that are the most (or least) of some property. And the array and property are arbitrary. Always.

I know I could just do something like:

009:0> array = %w(1 2 3 3)
["1", "2", "3", "3"]
010:0> max = array.map {|x| x.to_i}.max
3
011:0> array.find_all {|x| x.to_i == max}
["3", "3"]

But it looks clunky to me. What I really want are methods to do that for me, and I want methods that are a little more complicated, and generated via module_eval… and using a Proc. What I want, in short, is this:

module Enumerable
  [['min','-1'], ['max','1']].each do |name, comparator|

    module_eval %{
      def find_all_#{name}
        block = Proc.new do |object|
          block_given? ? yield(object) : object
        end

        self.inject([]) do |result, element|
          if result.empty?
            result << element
          else
            case block[element] <=> block[result.first]
            when 0
              result << element
            when #{comparator}
              [element]
            else
              result
            end
          end
        end
      end
    }
  end
end

It’s not the best way to handle this situation, but I’m on a metaprogramming diet, because that’s something I don’t have enough practice in. So I’m doing it more than I need to. I’m trying to get a feel of when a Proc is called for (har har). Given the way I’m doing the inject, I feel that the Proc is a good move. Maybe I’m wrong.

Comments

Safely overriding method_missing in a class that already has it

Today I’m making a ridiculous module. Pretty much all it does is override method_missing. There’s a big problem with that, though. What happens when the class you’re including the module in already has method_missing, and you don’t want to mess that up? After a few failed attempts, watching stack levels get too deep, and a little head-scratching, I made this:

module Hero

  def self.included(base)
    base.class_eval do
      unless method_defined? :method_missing
        def method_missing(meth, *args, &block); super; end
      end
      alias_method :old_method_missing, :method_missing
      alias_method :method_missing, :hero_method_missing
    end
  end

  def hero_method_missing(meth, *args, &block)
    if meth.to_s == 'hero'
      "Did I ever tell you you're my #{meth}?"
    else
      old_method_missing(meth, *args, &block)
    end
  end

end

There’s my trivial example. Hero is ready to be included in any class, method_missing defined or not. It’ll handle whenever you try to call a method named hero, and pass everything else on to whatever (the class’ original method_missing, or its parent’s method_missing). Now you can ruin ActiveRecord without totally ruining ActiveRecord!

The secret is defining method_missing in the class if it isn’t already, just super, so that the double alias_method is cool.

Take a look at it in action:

mvb:~ cms$ irb
001:0> load 'hero.rb'
true
002:0> class C;def method_missing(meth);"I'm fancy!";end;end
nil
003:0> C.new.hero
"I'm fancy!"
004:0> class C; include Hero; end
C
005:0> C.new.hero
"Did I ever tell you you're my hero?"
006:0> C.new.garbage!
"I'm fancy!"

I promise the actual use is more interesting, though possibly just as useless.

Comments (5)