Autovivification
Video transcript & code
Ruby goes to great lengths to be a low ceremony language. For instance, in Ruby there is no need to declare the types of variables or methods. There is no need to declare the instance variables in a class. And there is no need to declare local variables before they are assigned.
As an example, we can reference instance variables or global variables that don't exist.
@foo # => nil
$bar # => nil
Rather than raising an exception, Ruby treats these variables as if they have values of nil
.
It might seem as if what is happening here is that Ruby is spontaneously causing these variables to exist the moment they are referenced. The technical term for this kind of spontaneous generation is autovivification. Meaning that a variable is "automatically brought to life".
But if we ask Ruby whether the instance variable we just referenced exists, it claims it does not.
And if we ask for a list of the current instance variables, Ruby says there are none.
@foo # => nil
instance_variable_defined?("@foo") # => false
instance_variables # => []
So what is happening here is not really autovivification. Ruby is not causing these variables to be brought to life. Instead, it simply treats any missing instance variable as if it has a value of nil
.
As you no doubt know, Ruby behaves a little differently when it comes to local variables. Trying to reference a nonexistent local variable results in a name error.
foo # =>
# ~> NameError
# ~> undefined local variable or method `foo' for main:Object
# ~>
# ~> xmptmp-in26383sFG.rb:1:in `<main>'
And this is probably for the best. If Ruby silently returned nil
for every unrecognized local variable name, we'd have a terrible time hunting down typos.
However, there is a case where Ruby does, in fact, autovivify local variables. And if you don't know what's going on, you might be taken by surprise by Ruby's behavior.
Here's an example.
We assign a local variable x
.
We write an if
statement, saying that if x
is even, a new variable result
should be set to the the value of x
divided by two.
Then we check the value of result
.
Not surprisingly, the value is 1
.
And if we ask Ruby for a list of local variables, we see exactly what we expect: x
and result
.
x = 2
if x.even?
result = x / 2
end
result # => 1
local_variables # => [:x, :result]
Now let's set x
to 3
. Before we execute this, let's think about what we expect to happen.
Since x
is now odd, we expect that the if
body will not be executed. Since it won't be executed, we expect that the result
variable will not be assigned. Since it is never assigned, we expect that this variable will not be defined, and accessing it will raise a NameError
just like we saw before.
Let's check our assumptions.
x = 3
if x.even?
result = "even"
end
result # => nil
This is not what we expected!
Instead of a NameError
, we see that result
has a value of nil
.
Has Ruby suddenly started treating local variables the way it treats instance variables? Is it now acting as if any unset local variable has a value of nil
?
Let's check. We'll ask Ruby what local variables are defined.
x = 3
if x.even?
result = "even"
end
local_variables # => [:x, :result]
Ruby says that both x
and result
are defined, just like before.
But how can this be?! We know that no assignment to result
ever took place.
What we've run into here is a very special rule in how Ruby evaluates code. In a nutshell, the rule says that at a given line in the code, if there is any possibility that a local variable will exist, then that variable is guaranteed to always exist. Even if Ruby has to quietly assign it a nil
value to ensure its existence.
In effect when Ruby sees a case where a local variable might be created, Ruby rewrites our code to behave like this:
x = 3
result = nil
if x.even?
result = "even"
end
Here, the result is always assigned a nil
value.
Then the value is optionally overridden if x
turns out to be an even number.
In effect, Ruby autovivifies the result
variable.
This rule has some interesting implications. For instance, check out this code. We have a simple method which takes an object and just sends #to_s
to it.
Then we write foo = stringify(foo)
.
Surely this can't be valid, right? We're assigning the result of the method to a new variable, but we're also using that variable as an input to the method!
And yet, when we execute this, it works just fine, and returns the string equivalent of nil
.
def stringify(o)
o.to_s
end
foo = stringify(foo)
# => ""
This is another example of Ruby's implicit rewriting rules in action. Ruby ensures that it's not possible for a variable to be both existent and nonexistent on the same line of code.
So what do these strange rules about assignment mean for our code? Well, not a lot really.
It does mean that there's no need to write code like this, where we provide an else clause to assign a variable the value nil
in case an if
evaluates to false.
Ruby will take care of this for us, so we can omit the else.
if x.even?
result = "even"
else
result = nil
end
Other than that, the main value of knowing about this autovivification rule is that it can save us from confusion. If you've ever run into case where a variable was set to nil
even though no code had run which should have set it, now you know how it happened.
Before I go, I'd like to thank Todd A. Jacobs for suggesting today's topic. Thanks Todd, and happy hacking!
Responses