An Example of Ruby Introspection

Now, let's create an instance of this class:

someone = Person.new

Suppose later in the program you want to see if the person has been named. Here is a way to do it:

if someone.name.empty?
    puts "no name"
end

That's simple enough, but probably wrong.

The problem is that if the name property has not been initialized, its value is nil and the NilClass does not have an empty? method. In this case the code will fail with an error:

NoMethodError: undefined method `empty?' for nil:NilClass

So the safe way to do this is to check if the value is nil before you check if it is empty. Like so:

if someone.name.nil? || someone.name.empty?
    puts "no name"
end

This is great, except it gets very tedious and can be error prone. Testing for nil is sort of like checking return codes for function failures: it's a good thing to do but it's easy to forget and it adds a lot of crud to your code. The solution for the return code problem is to use exceptions. The solution to the nil problem is to use the power of Ruby introspection to make a small change to the way the NilClass works.

Ruby, unlike Java, lays its entire guts out for you to use and modify. So, I can go into the NilClass and give it an empty? method that works in a reasonable fashion.

class NilClass
    def empty?
        true
    end
 end

What we are saying here is that something that is nil should also be considered empty. Seems reasonable enough. Best of all, with this change the first test case—without the explicit test for a nil value—now works without error.

Redefining basic language constructs in this way is generally a very bad thing to do. I believe, however, you can break the rules if done in a judicious and considered manner. This is one case where the change is so simple to understand and benefit is so great, I think it's worth doing.

Here is what Dave Thomas says in the Pickaxe book:

One of the many advantages of dynamic languages such as Ruby is the ability to introspect—to examine aspects of the program from within the program itself. Java, for one, calls this feature reflection but Ruby's capabilities go beyond Java's.

I'd be curious if the Ruby practitioners would agree my example is a good use of introspection? And if so, where is your line on modifying system constructs?

Comments

Comments have been closed for this entry.

a step further

You could take it even further, like this:

class NilClass
def nil?()
return nil
end
end

Now there's no need to change any other code anywhere, and you'll never get an exception again from accidentally chaining a nil. Since nil naturally evaluates to false, pre-existing code might still work OK, too. Scary though... seems too radical not to have serious consequences somewhere down the line.

re: An Example of Ruby Introspection

I do like this particular 'fix' for NilClass #empty? It solves a slightly messy recurring issue with chaining method calls.

Unfortunately, it seems like confusion would arise, because Nil's so fundamental a concept in the language.

re: An Example of Ruby Introspection

I don't know what's all the fuzz this Ruby stuff but I use Visualbasic and can probably do more than string.empty? because in Visualbasic we can assign strings like mystr = "something" so it's not empty. Anyhow, why one would have an empty string?

re: An Example of Ruby Introspection

I like the idea to add the empty? method to the NilClass.
There were too many times when I tried to remember, "Did I check my results right? Did I do the right thing?".

Don't get me wrong; I LOVE Ruby and friends and use it almost daily ... but that empty? or nil? drives me bananas at times.

re: An Example of Ruby Introspection

I think the reason Ruby doesn't include something like this is that people don't use empty? in the first place. It's redundant. Instead of

  if someone.name.empty?
    puts "no name"
  end

...just use...

  if not name
    puts 'no name'
  end

Cheers

re: An Example of Ruby Introspection

BTW - what you did isn't actually introspection. Introspection involves programmatically looking at the existing code (e.g. calling "methods"), not simply adding to it.

Hope you enjoy your programming...

re: An Example of Ruby Introspection

irb(main):006:0> name = ""
=> ""
irb(main):007:0> if not name ; puts "no name" ; end
=> nil
irb(main):008:0> if name.empty? ; puts "no name" ; end
no name
=> nil