Friday, March 30, 2007

Gotcha with Ruby "upcase!" and "downcase!"

Ruby String class provides a method called upcase that converts all characters of a string to upper case. Ruby also provides an equivalent (downcase) to convert all the characters of the string to lower case. The return value of upcase or downcase is a copy of the string with all the characters converted to upper or lower case. To convert the string in place, there are upcase! and downcase!. The gotcha comes from these two methods. If a string (say, "HELLO WORLD") is all upper case, then invoking upcase! on that string returns nil. This is different from how upcase and downcase function - they return a copy of the string even if all the characters are upper case. This could be a big gotcha!

Examples -

my_string = "Hello World"
my_string1 = "HELLO WORLD"

ret_val = my_string.upcase
ret_val1 = my_string1.upcase

Both ret_val and ret_val1 are "HELLO WORLD".

Now, consider the following -

gotcha_string = "Hello World"
gotcha_string1 = "HELLO WORLD"

ret_val = gotcha_string.upcase!
ret_val1 = gotcha_string1.upcase!

Here, ret_val is "HELLO WORLD" whereas ret_val1 is "nil".

We would expect ret_val1 to also be "HELLO WORLD". So, if we used the bang versions of upcase and downcase like we would use the non-bang versions, we would be in for a rude surprise.

In my opinion, this violates the Principle of Least Surprise that Ruby supposedly adheres to.

Update
I just want to clarify that the value of gotcha_string1 itself doesn't become nil, just the return value of calling that variable is nil. gotcha_string1 will still be "HELLO WORLD". Its return value which is assigned to ret_val1 will be nil. As the commenter below suggests, do not use the "!" version of upcase or downcase in an assignment or do not depend on their return value.





9 comments:

Anonymous said...

Actually it's not that much of a gotcha. Ruby's convention with methods that end with a '!' is that the method would do something surprising. In this case, upcase! changes String instances in place. So you shouldn't depend on the return value nor use it in an assignment.

The convention is:
some_string.upcase!

Morampudi Bhanu said...

You are right. Thank you for clarifying.

Anonymous said...

RTFM

Anonymous said...

POLS only applies to matz and to people experienced with Ruby who know what to expect because they have come to think like him.

Those who invoke POLS are (nearly) invariably newbies who don't understand the above.

Anonymous said...

Great post, I think a lot of people write their own ! methods (me included) without following this convention, return the new value if it changes - nil if its unchanged.

And anonymous - surely POLS is something everyone can benefit from, so that we don't have to RTFM

Anonymous said...

I actually think this is useful. It's according to convention that the string is changed in-place; but this way you can check the return value to find out whether the change actually changed the value, which strikes me as potentially valuable information.

Grant said...

Hi Bhanu, I'm looking for cool rails developers for shopwell.com in Palo Alto. If you know of anyone please put them in touch. Thanks.
~ Grant

Nate Wiger said...

It returns nil ON PURPOSE so you can test for truth

if some_string.upcase!

This is very useful in regexes

if some_string.gsub!(/pat/,'')

RTFM

Anonymous said...

The reason why it returns nil is simply because the ! methods don't return anything, and thus the return value is implicitely nil.