Spheres of visibility, like rooms in a house

When we talked about variables we mentioned that the most common type of variables is the “local variable”, without explaining any further why they are local. Local to what? Where? We’re finally ready to explain that more.

Since we have now talked about methods, we can also discuss another important concept: scopes.

Wikipedia says: “In programming, the scope of a name is the part of a program where the name is valid: where the name can be used to refer to something else.” (slightly modified to match our own terminology)

Names are known (or defined) in a certain scope, and unknown (or undefined) outside of this scope.

You can think about a scope as of a sphere, a bubble, or a room (with no windows, and doors closed): Certain names, like variable names, are “known” and “visible” inside of the room. Other names, known and visible in another room are not known in this room, but only in the other room.

Every time there is a method call, and the flow of execution enters the method’s body, it enters a new scope, or “room”. Things that are “local” to this method’s scope (i.e. things that are “inside” of the room), are only visible in this scope. Outside of it, they are unknown.

Undefined local variable or method

This is also a good opportunity to talk about an error message that you might see most often. E.g. you’ll see it every time when you made a typo and misspelled a variable or method name.

Consider this code:

def add_two(number)
  number + 2

puts add_two(3)
puts number

The line puts add_two(2) will output 5, but the line puts number will then raise an error.

This is because the variable number that is assigned the number 3 when we call the method is a local variable. It is local to the method’s scope. It is created when the flow of execution enters the method.

Outside of this scope, when the flow of execution has returned from the method the method’s scope has been destroyed, and all local variables are gone. The local variable number is therefore not known, and Ruby raises an error saying undefined local variable or method 'number'.

If you think about this error message for little bit, does it make sense to you?

We have silently skipped over the fact that, in Ruby, both local variable names and method names, are written the same way: they’re just plain words. For example, here:

number = 2
puts number

number is a local variable, and it is used in the line puts number.

However, here:

def number
puts number

number is the name of a method. And it can be used (called) in the exact same way: puts number

This is because Ruby, when it executes a program, evaluates one statement after another. And when it encounters a plain word like number then it will first check if, within the current scope, it knows a local variable with the same name. If so, it will use the value that is associated to this variable. If there’s no local variable with this name, then it will look for a method. If there’s also no method with this name it will then raise the error message undefined local variable or method 'number'.

So the error message is pretty precise, but also sounds kind of convoluted. What it basically tries to say is:

You’ve used the word “number” here, and i don’t know it (in this scope). Did you mean to use a local variable? Or a method?

Does that make sense?

Back to the topic of local scopes. Let’s look at another example:

number = 1

def add_to(number)
  number + 2

puts add_to(3)

What do you think the output will be? Will it be 3, or 5? Something else?

If you run the code you will see that it’s 5.

The reason for that is that we do assign the number 1 in the outer scope to a variable number, but this variable is then never used: the only other line in the outer scope is the last line puts add_to(3), and this line does not use the variable number.

Instead, when the control flow enters the method add_to Ruby will create a new local scope, and it will define a new local variable number which is assigned the number 3 that was passed to the method. This new variable number is local to the method’s scope, and therefore this is a different variable than the one on the very first line, in the outer scope.

We found the following a good metaphor for scopes:

When Ruby enters a method, it’s like she enters a shiny new room in a house. With her, she brings the objects that are passed as arguments to the method call. In the example above she brings an object that is the number 3.

Now, as soon as Ruby enters the method, she sticks post-it notes on the objects, according to the argument list from the method definition. In our example that is the name number. So from now on, in this room, there’s a known local variable that has a value assigned: The object (number) 3 with the post-it note number on it.

In our example, the outer scope and the scope of the method add_two, are two different rooms, and there are two different post-it notes stuck on two different numbers, which just happen to have the same name on them.