IT Staff

Blog về chuyên ngành IT

Ruby’s defined? Operator

leave a comment »

Ruby’s defined? Operator

 
Rate This

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:

1 class Person
2   attr_accessor :first_name, :last_name
3  
4   def full_name
5     return @full_name if defined?(@full_name)
6     @full_name = "#{first_name} #{last_name}"
7   end
8 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:

1 ruby-1.9.2-p0 > defined? Person
2  => nil
3 ruby-1.9.2-p0 > class Person
4 ruby-1.9.2-p0 ?>  end
5  => nil
6 ruby-1.9.2-p0 > defined? Person
7  => "constant"

It works on methods:

1 ruby-1.9.2-p0 > def bark
2 ruby-1.9.2-p0 ?>  puts "woof"
3 ruby-1.9.2-p0 ?>  end
4  => nil
5 ruby-1.9.2-p0 > defined? bark
6  => "method"

And of course it works on variables of all kinds:

01 ruby-1.9.2-p0 > defined? @@a
02  => nil
03 ruby-1.9.2-p0 > @@a = 'a'
04  => "a"
05 ruby-1.9.2-p0 > defined? @@a
06  => "class variable"
07 ruby-1.9.2-p0 > defined? @b
08  => nil
09 ruby-1.9.2-p0 > @b = 'b'
10  => "b"
11 ruby-1.9.2-p0 > defined? @b
12  => "instance-variable"
13 ruby-1.9.2-p0 > defined? c
14  => nil
15 ruby-1.9.2-p0 > c = 'c'
16  => "c"
17 ruby-1.9.2-p0 > defined? c
18  => "local-variable"

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

1 ruby-1.9.2-p0 > defined?(+)
2 SyntaxError: (irb):1: syntax error, unexpected ')'
3     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:

01 ruby-1.9.2-p0 > x = 'test variable'
02  => "test variable"
03 ruby-1.9.2-p0 > defined? x
04  => "local-variable"
05 ruby-1.9.2-p0 > def defined? object
06 ruby-1.9.2-p0 ?>  puts "defined? has been overridden!"
07 ruby-1.9.2-p0 ?>  end
08  => nil
09 ruby-1.9.2-p0 > defined? x
10  => "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:

1 ruby-1.9.2-p0 > @x.nil?
2  => true
3 ruby-1.9.2-p0 > @x.defined?
4 NoMethodError: undefined method `defined?' for nil:NilClass
5     from (irb):8
6     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.

Written by Xavier

Tháng Tám 3, 2013 lúc 9:57 sáng

Posted in RoR, Ruby

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s

%d bloggers like this: