16 December, 2010

A compact idiom for some case-expression usages.

Motivation

At some point every Ruby programmer has written this pattern:

result = case item
when String
  string = item
  "item is a string: #{string}"
when Fixnum, Float
  num = item
  "item is a num: #{num}"
end

I could use directly the item variable in each when block, but this would make the code harder to read. Wouldn't it be nice if we had the item in a variable with a meaningful name plus and with no boilerplate?

Metaprogramming
We'll take advantage of Ruby's powerful metaprogramming capabilities to design a pattern that describes exactly what we want. This is the best I've come up with:

result = item.is do
  a String do |string|
    "item is a string: #{string}"
  end
  a Fixnum, Float do |number|
    "item is a number: #{number}"
  end
end

Implementation
This is how I would write it:

class IsArray < Array
  def a(*categories, &block)
    self.push({:categories => categories, :block => block})
  end    
end

class Object
  def is(&block)
    IsArray.new.instance_eval(&block).detect do |hash|
      if hash[:categories].any? { |category| category === self }
        break hash[:block] ? hash[:block].call(self) : nil
      end 
    end
  end
end

No comments:

Post a Comment