Posts Tagged ‘yield’

Ruby’s defined? Operator

December 20, 2010

Even if you’ve used Ruby’s defined? operator on a daily basis, you may not understand how it works. I sure didn’t until recently, but it’s worth a look.

A refresher in memoization etiquette

If you’re acquainted with memoization, this might look familiar:

class Person
  attr_accessor :first_name, :last_name

  def full_name
    return @full_name if defined?(@full_name)
    @full_name = "#{first_name} #{last_name}"
  end
end

The full_name method above uses memoization – the return value of the method is calculated just once, on the first call. It’s stored in the instance variable @full_name, and used for subsequent calls to the method. I first discovered this technique digging through the code base for Thoughtbot’s shoulda gem. I’ve used it hundreds of times, and never really questioned how the defined? operator works until recently.

Question mark?

Ruby methods can contain some non-alphanumeric characters like “!” and “?”, and rubyists take advantage of this to add readability to our code. Methods ending in “!” typically mean one of two things: the method is altering its receiver, or it’s going to complain loudly if it fails (usually by raising an exception). By the same convention, methods ending in “?” are asking a question, and the answer is usually boolean (yes/no).

The defined? method follows this convention…sort of. I always assumed it returned true/false, but that’s only half the story. If the object in question is defined, defined? gives you a string description of the object. This equates to “true” in any conditional arguments. If the object is not defined, it returns nil, which equates to “false”.

Test anything. Almost.

So defined? works in any boolean context, but it also provides a little more info. And it works on just about anything. Classes:

ruby-1.9.2-p0 > defined? Person
 => nil 
ruby-1.9.2-p0 > class Person
ruby-1.9.2-p0 ?>  end
 => nil 
ruby-1.9.2-p0 > defined? Person
 => "constant" 

It works on methods:

ruby-1.9.2-p0 > def bark
ruby-1.9.2-p0 ?>  puts "woof"
ruby-1.9.2-p0 ?>  end
 => nil 
ruby-1.9.2-p0 > defined? bark
 => "method" 

And of course it works on variables of all kinds:

ruby-1.9.2-p0 > defined? @@a
 => nil 
ruby-1.9.2-p0 > @@a = 'a'
 => "a" 
ruby-1.9.2-p0 > defined? @@a
 => "class variable" 
ruby-1.9.2-p0 > defined? @b
 => nil 
ruby-1.9.2-p0 > @b = 'b'
 => "b" 
ruby-1.9.2-p0 > defined? @b
 => "instance-variable" 
ruby-1.9.2-p0 > defined? c
 => nil 
ruby-1.9.2-p0 > c = 'c'
 => "c" 
ruby-1.9.2-p0 > defined? c
 => "local-variable" 

I even tried other operators, just on a whim. But of course, this was too much to hope for :)

ruby-1.9.2-p0 > defined?(+)
SyntaxError: (irb):1: syntax error, unexpected ')'
	from /Users/bellmyer/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

defined? is an operator, not a method

Because you can enclose the object you want to check in parentheses (as in, defined?(@full_name)), you might be tempted to think it’s a method. It’s not, it’s a native operator. This is an important distinction, because it means defined? can’t be overridden:

ruby-1.9.2-p0 > x = 'test variable'
 => "test variable" 
ruby-1.9.2-p0 > defined? x
 => "local-variable" 
ruby-1.9.2-p0 > def defined? object
ruby-1.9.2-p0 ?>  puts "defined? has been overridden!"
ruby-1.9.2-p0 ?>  end
 => nil 
ruby-1.9.2-p0 > defined? x
 => "local-variable" 

I don’t get an error trying to override the operator with a method definition, but it doesn’t work, either. Honestly, Ruby is so permissive I half expected the override to work anyway! Another clue that you’re dealing with an operator is that it has no receiver. That’s why you give it the object, instead of calling it from a receiver, like the nil? method:

ruby-1.9.2-p0 > @x.nil?
 => true 
ruby-1.9.2-p0 > @x.defined?
NoMethodError: undefined method `defined?' for nil:NilClass
	from (irb):8
	from /Users/bellmyer/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

While defined? is most valuable (and most commonly used) in a boolean context, there may be meta-programming applications where you’d want to what type of “thing” you’re dealing with. While you can always use the .class method, you have to already know that the object is defined. In the world of meta-programming, that’s often a luxury you don’t have.

Announcement: Kansas City Ruby User Group Meeting

January 11, 2010

Every month, the Kansas City Ruby User group meets to discuss current events in the development world, and learn from each other. This month, Shashank Date will be offering a refresher of his talk from last year on Procs and lambdas. Often, Ruby on Rails developers can be strong on Rails, and light on Ruby.

This talk covers one of the most powerful Ruby features that is used all the time by the Rails framework itself. Newer developers, however, may not realize just how easy it is to harness the power of blocks, and by extension Proc and lambda calls, in methods. I might also stand up and give a brief rundown on binding, a closely related concept.

The Where and When

If you live in the Kansas City area, join us Tuesday January 11, 2010 at 6:30pm here:

Centriq Foss
8700 State Line Road
Leawood, KS 66206

Visit the meetup page for more info.

Preview

To give some background, here’s a code snippet that illustrates a block:

def do_for_each(array=[], &amp;block)
   array.each{|element| block.call element}
 end

do_for_each([1,2,3]){|x| puts x}

A Ruby block is a chunk of code that can be passed to a method and run at will. This code prints each element of the array on its own line. If this looks familiar, that’s because it’s really a lamer version of the Array class’ map instance method.

When you pass a block to a method like this, you’re recreating a Proc object implicitly. You don’t have to do anything extra, and Ruby knows what to do with this extra code hanging off the method call. But what if, for instance, you needed to pass *two* blocks into a method? Here’s one way:

def custom_print(words, quiet, loud)
  words.each do |word|
    if word =~ /!$/
      puts loud.call(word)
    else
      puts quiet.call(word)
    end
  end
end

words = ['hello', 'world!', 'turkey']
custom_print(words, lambda{|w| w + '.'}, lambda{|w| w.upcase})

In this example, words are “loud” if they end in an exclamation point. I chose to end quiet words with a period, and to capitalize loud words. Please join us Tuesday for a much more detailed explanation.


Follow

Get every new post delivered to your Inbox.