I’ve touched on this gotcha briefly in the past when discussing the Wat video, but I thought a few examples of when single-line conditionals can bite you would be fun.
In Ruby, we can write a conditional containing a single expression that normally takes up three lines:
unless condition something end
on a single line to save space:
something unless condition
And that is all well and good – these two are pretty much the same. But they’re not identical in practice. There are a few weird things about to come up.
Our first example will be using
defined? to conditionally print a variable
if defined?(var1) puts var1 # never runs end var1 # NameError: undefined local variable
So that works just as we’d expect. The conditional never runs because
nil. After the conditional, access the undefined
var1 gives us a
NameError because (appropriately) its not defined. Let’s modify that a little bit and put an assignment inside of the conditional.
if defined?(var2) var2 = 5 # never runs end var2 # nil defined?(var2) # "local-variable"
So that might look a bit odd. We never ran the conditional, so
var2 never gets set – that makes sense. But after the block,
var2 doesn’t throw a
NameError when accessed anymore. This is because the Ruby parser makes room and defines
var2 when it sees it on the lefthand side of an expression (even though its inside of a conditional that doesn’t run).
Let’s write the same on one line though:
var3 = 5 if defined?(var3) var3 # 5
Even more interesting – an undefined variable written this way will become defined and assigned when run. The first thing that happens is that the parser comes along and defines
var3 which it sees on the lefthand side of a conditional. Then
defined? runs, which this time evaluates to
"local-variable", causing the conditional to pass, and
5 to be assigned to
var3. In cases like this, the single-line conditional will produce an entirely different result than block conditionals.