Ever spend a long time debugging something, only to find out an hour or two later that you were staring at the problem all along, and you should have realized it from the beginning? And then, of course, the solution only requires a tiny tweak to one line, perhaps merely the addition of a few parenthesis.
>> defined? RAILS_ENV => "constant" >> defined? RAILS_ENV && RAILS_ENV == "development" => "expression" >> (defined? RAILS_ENV) && (RAILS_ENV == "development") => false
D’oh! (let’s ignore for now the issue of whether or not the “fixed” code is reasonable to run anyway)
I was curious to see if the expression was evaluated, and it appears that it is… sort of:
>> def foo >> $stderr.puts ":foo" >> end => nil >> def bar >> $stderr.puts ":bar" >> raise "hell" >> end => nil >> defined? foo => "method" >> defined? bar => "method" >> defined? foo || 1 :foo => "expression" :bar >> defined? bar || 1 => nil
Notice: the methods were executed, but the exception from bar was caught and squirreled away somewhere, and defined? the returned nil, even though the method and expression were certainly “defined”.
Tags: ruby
This is some great exposition on a corner of Ruby I knew little about. Thanks!
can you explain why this is the case?
@brandon: Why is what the case?
Why is the entire expression passed to
defined?, rather than just the first arg? becausedefined?isn’t a method call but a keyword, and it has very low precedence.defined? CONST and CONST == "foo"would have given the expected result, even without parentheses, because of the lower precedence ofand.Why is the expression evaluated? I dunno; Principle of Least Surprise, I guess. I’d need to ask Matz (or ruby-talk) to be certain. The weird thing is that pg 113 of “The Ruby Programming Language” states that “the expression that is the operand to
defined?is not actually evaluated; it is simply checked to see whether it could be evaluated without error.” But that seems to contradict by my irb session, pasted in the post. The methods were clearly evaluated (allowing the printing and raising exception side effects).Initially, I was surprised that the exception gets eaten. But I supposed that’s to be expected as well: Calling
defined?will never throw an exception, but will always return a truthy (string) or falsy (nil) value.Does that help?
The issue is the `&&` and `and` have different levels of precedence. I use `defined?(ARG)` with parentheses on a regular basis to avoid this issue.