Sunday, November 25, 2007

Loving to_proc

Anytime I'm pairing with a developer who isn't familiar with ActiveSupport's Symbol#to_proc extension, it's fun to see their reaction when they see how it works. It's the most radical example I've seen of a simple (and pure-Ruby) extension to a core class allowing for awesome improvements in readability.

[If you're not familiar with the method, read about it here or here. For a bizarre step beyond (that I'm not personally fond of) take a look at this old Dr. Nic post.]

The one frustrating thing about it is that it's so limited: you can only send one method with no arguments to each yielded object. If you need to pass arguments or do any more complex calculation, you're back to passing an associated block the old-fashioned way.

A couple of weeks ago Patrick and I were pairing and noticed a beneficial side-effect of the limitations of Symbol#to_proc: sometimes when you can't use it right off the bat, it's because the behavior you were going to put in the block would be better off living in the objects you're working with.

Here's a simple example. Imagine you want to expose the area codes represented in a collection of phone numbers. Your initial implementation might look like this.

def area_codes
  self.phone_numbers.collect do |phone_number|
    phone_number[0..2]
  end.uniq
end

"Shame that block's so ugly," you might think. Well you're right! It is a shame, and it doesn't have to be that way. It's ugly because it knows about the guts of phone numbers. If phone numbers knew more about themselves, using them could be prettier.

def area_codes
  self.phone_numbers.collect(&:area_code).uniq
end

Of course sometimes you really need to pass arguments. We toyed around with introducing an Array#to_proc that looked like this.

Array.class_eval do
  def to_proc
    lambda {|target| target.send *self}
  end
end

[1,2,3,4,5].select &[:between?, 2, 4]  # => [2, 3, 4]

In the end we decided that, while nicely brief, it wasn't pretty enough to put into our code base -- too much punctuation -- and stuck with the old-fashioned way.

Are there any other core extensions that have really floated your boat? Do share.

Wednesday, November 14, 2007

Mix It Up

Mix, Oracle's new social networking site, is up and running. It's pretty nice looking.

If you love working on a Mac, why don't you register and vote for Toby's idea? Oracle's lack of a native client for Intel Macs is crazy, and this is a good opportunity to let Oracle know how many of us are suffering from it. (The idea is actually about providing XE for the Mac, but it's still a good rallying point.)

Personally, I've spent about a year on a project where we deploy on Oracle in production but develop on Postgres because of the lack of Mac support. Our Cruise build runs against Oracle, so when there are issues we see them pretty quickly, but not quickly enough. The turnaround time is something like ten minutes instead of something like ten seconds, which often means a context switch. It's lame. Oracle can fix it.

Friday, November 02, 2007

Renum 0.0.3

This morning at RubyConf I updated Renum so that when the enum declaration appears inside a class or module, the enum class gets nested appropriately. So this works the way you'd expect.

module MyNamespace
  enum :FooValue, [ :Bar, :Baz, :Bat ]
end

I also made EnumeratedValue comparable, where the natural ordering matches the array of value names given in the declaration.

Those features are in renum-0.0.3. (renum-0.0.2 was a botched release where I left a new class out of the gem manifest. Apparently I need to improve my pre-release package verification.)

Renum -- a Ruby enum gem

A while back I blogged through trying to implement something in Ruby similar to what you get from Java and C#'s enum keyword (and before there was a keyword, from the type-safe enumeration pattern in Java). It went pretty well.

I mentioned there that I might go ahead and package the thing (or something like it) up as a gem. Tonight I did. It's called Renum.

Renum lets you create enums like this:

enum :Status, [ :NOT_STARTED, :IN_PROGRESS, :COMPLETE ]

enum :Color, [ :RED, :GREEN, :BLUE ] do
  def abbr
    name[0..0]
  end
end

As of version 0.0.1, released just minutes ago, those enums work like this:

describe "enum" do
  
  it "creates a class for the value type" do
    Status.class.should == Class
  end
  
  it "makes each value an instance of the value type" do
    Status::NOT_STARTED.class.should == Status
  end
  
  it "exposes array of values" do
    Status.values.should == [Status::NOT_STARTED, Status::IN_PROGRESS, Status::COMPLETE]
  end
  
  it "enumerates over values" do
    Status.map {|s| s.name}.should == %w[NOT_STARTED IN_PROGRESS COMPLETE]
  end
  
  it "indexes values" do
    Status[2].should == Status::COMPLETE
    Color[0].should == Color::RED
  end
  
  it "provides index lookup on values" do
    Status::IN_PROGRESS.index.should == 1
    Color::GREEN.index.should == 1
  end
  
  it "allows an associated block to define instance methods" do
    Color::RED.abbr.should == "R"
  end
  
  it "provides a reasonable to_s for values" do
    Status::NOT_STARTED.to_s.should == "Status::NOT_STARTED"
  end
  
end

Hopefully someone will find this useful. There are some obvious features to add, like value lookup by name. There are also a lot of things that could be locked down a little, but the library's useful without that so I'm not sure it's worthwhile. (For example, I don't currently prevent you calling Color.new 'HAZEL'.)

If this is something you might use, let me know what you want out of it.

Also, tonight I arrived in Charlotte for RubyConf. Say hi if you see me.