Lesson 03 / 12
Variables and Constants
In Ruby, assignment is syntax rather than a message; local variables, instance variables, global variables, and constants are told apart by the sigil on the name; the limits measured with defined?.
Published Updated
In this lesson
- Tells local variables, instance variables, global variables, and constants apart by their scope sigils
- Measures assignment with defined? and shows that it is syntax rather than a message
- Separates compound assignment (+=, ||=) as assignment, and the addition inside it as a message
Contents
The previous lesson established that every value is an object belonging to a class and that the receiver’s class answers the message. One question was left open: how is a name bound to one of these objects? When x = 5 is written, is a message going to x, or to 5, or is there no message at all? The concept of a variable itself was explained in the Programming Fundamentals course (M01/K02); it is not repeated here. This lesson measures only the Ruby-specific question: when a name is bound to an object, who is at work?
In Ruby, the mark at the start of a name determines what the name is. We call these marks sigils: a lowercase name with no mark is a local variable, a name starting with @ is an instance variable, a name starting with $ is a global variable, and a name starting with an uppercase letter is a constant. There is also a class variable that starts with @@; it is returned to in the Scope Limits and Parallel Assignment section. The file below queries the four marks, an undefined constant, and an assignment one by one with defined?.
The Name’s Sigil Determines the Kind
defined? is an expression that says what an expression is without running it. For an undefined name it returns nil; for a defined one, a string saying what it is. This is the measuring instrument we will use in the lesson.
# scope_sigils.rb — the sigil on a name determines the kind of variable x = 5 @a = 1 $g = 2 puts defined?(x) puts defined?(@a) puts defined?($g) puts defined?(String) p defined?(Missing) puts defined?(result = 5)
local-variable instance-variable global-variable constant nil assignment
Six queries, six different answers: x is a local variable, @a an instance variable, $g a global variable, String a constant. For the undefined constant Missing, nil was returned. The last line is the real subject: the expression result = 5 was reported as "assignment". Neither "method" nor "expression"; assignment has a name of its own.
LB7. In this lesson, “assignment” is the spelling that binds a name to an object with =. defined? only reads the kind of the expression and does not actually run the assignment; for this reason the name result above never came into being. It is known that reassigning to a constant name prints a warning, but because that warning carries a file path it is not printed or measured here; it is mentioned only in prose.
A Constant Binds the Name and Does Not Freeze the Object
A name starting with an uppercase letter is a constant. Whether a constant is defined can be read with Object.const_get. But what does a constant make constant: the object the name is bound to, or the binding of the name? The file below binds a number and an array to one constant each, and then adds an element to the array.
# constant_array.rb — a constant binds the name, it does not freeze the object LIMIT = 10 LIST = [1] puts Object.const_get(:LIMIT) puts defined?(LIMIT) LIST << 2 p LIST puts LIST.frozen? puts LIMIT.frozen?
10 constant [1, 2] false true
Although the array was bound to the constant LIST, it changed: the constant marks with a warning that the name is being bound to a different object, but it does not lock the contents of the object. The array is not frozen (false). Numbers, on the other hand, are already immutable objects, and frozen? returns true for them. Someone who really wants to make a constant immutable freezes the object separately; freezing is measured on strings in the Symbols and Strings lesson of this course.
Each Kind Is Kept in Its Own Place
The sigils are not only a convenience of spelling; each kind is stored in a different place. Local variables belong to the scope of the running code itself, instance variables are tied to an object, and global variables are visible across the whole program. Ruby offers methods that list these three. The file below defines the three kinds at the top level and looks each one up in its own list.
# variable_lists.rb — each kind is kept in its own list x = 5 @a = 1 $g = 2 p local_variables p instance_variables puts global_variables.include?(:$g) puts self.inspect puts self.class
[:x] [:@a] true main Object
x was found in the list of local variables and @a in the list of instance variables; $g is in the list of global variables. The answer to the question of whose instance variable it is, is self: at the top level, self is the main object of the file, it prints as main, and its class is Object. So the @a written at the top level is bound to an instance of Object; an instance variable is always a variable of an object.
Assignment Is Not a Message
The rule of the previous lesson was: 5 + 3 is a message, and its responder is the class. The same question can be asked of assignment: if x = 5 is a message, there must be an owner that answers it. The file below puts three things side by side: plain assignment, a method call that returns a value, and the return value of an assignment.
# assignment_value.rb — an assignment expression carries a value puts defined?(a = 1) puts defined?(puts) puts defined?(1 + 2) value = (b = 7) puts value puts b
assignment method method 7 7
Three expressions gave three different kinds: the assignment "assignment", the puts call "method", the addition "method". So 1 + 2 and puts are in the same category, and a = 1 is separate. Even so, an assignment carries a value: the value of the expression (b = 7) is 7, and b was bound as a name. These two observations together say that an assignment is an expression but not a method call.
Scope Limits and Parallel Assignment
This section measures where names are visible. defined? reports only the kind of an expression without running it; here it is used as a scope probe. The axis is syntax: whether a name is visible or not is determined by the scope rules of the language, not by a method defined by a class. Three questions are asked: does a method opened with def see the outer local variable, does a block see it, and what kind is a name marked with @@ opened in a class body?
# scope_measurement.rb — how a method and a block see an outer local
outer = 10
def method_inside
defined?(outer).inspect
end
puts method_inside
block_result = [1].map { defined?(outer) }
puts block_result.first
class Record
@@count = 0
def self.counter_kind
defined?(@@count)
end
end
puts Record.counter_kind
nil local-variable class variable
Three measurements gave three different results. When the outer local variable outer was asked about from inside a method opened with def, defined?(outer).inspect printed nil: a method body starts a new scope, and the outer locals do not pass inside. When the same name was asked about from inside a block, "local-variable" was returned: a block sees the locals of the place where it was defined. For @@count the result was "class variable"; this kind, opened in a class body, is a separate category from the @ and $ marks. What these three results have in common is that no message answers any of them: the method not seeing outside, the block seeing, and the kind of the @@ mark all come from the spelling rules of the language. Where a name is defined matters as much as where it is asked: the same name outer gave two different answers when asked from two different places.
# parallel_assignment.rb — a, b = b, a is an assignment a = 1 b = 2 a, b = b, a puts a puts b puts defined?(a = 3)
2 1 assignment
The parallel assignment a, b = b, a swapped the values of the two names: a is now 2 and b is 1. A third temporary variable was not needed, because the right side was evaluated before the names on the left were rebound. defined?(a = 3) reported "assignment", which agrees with the finding of the previous section: assignment is the spelling of the language, not a message. defined? was asked here of a single assignment, not of the parallel assignment itself; there is also no trace in the output showing that the swap was done with a method call. Swapping two variables is a one-line operation in Ruby; this shows once more that names are labels pointing at objects, and the value is the object behind the label.
LB8. The class variable (@@) was probed only with defined?. How a class variable is shared under inheritance is not the subject of this course and was not measured.
Assignment Chaining and Destructuring
That assignment is the spelling of the language was measured. This section probes three additional forms of assignment: chained assignment, destructuring into multiple targets, and a missing right side. Each asks the same question: do the names point to the object, or do they copy the value? The axis is syntax; only on one line the owner of the << message is also printed, because after a chained assignment what changes the object is a message.
# assignment_chain.rb — chained assignment and destructuring x = y = [1] puts x.equal?(y) y << 2 puts x.inspect puts x.method(:<<).owner head, *rest = 10, 20, 30 puts head puts rest.inspect first, = [7, 8] puts first p1, p2 = 1 puts p2.inspect puts defined?(Math) puts defined?(puts) puts defined?(String.new)
true [1, 2] Array 10 [20, 30] 7 nil constant method method
The spelling x = y = [1] bound both names to the same object: x.equal?(y) gave true. This is why, when the array changed through y << 2, x also showed [1, 2]. What made the change here is not the assignment but the << message, and its owner turned out to be Array. So the assignment did not copy the object; it only bound a name, and when a message that changes the object arrived, all the names bound to the same object saw the change. This is a test of the intuition built in the earlier sections: a name is a label, and the value is the object behind the label. Assigning a label to another label does not duplicate the object.
The destructuring forms are also the spelling of the language. head, *rest = 10, 20, 30 gave the first value to head and the remaining ones to an array: head became 10 and rest became [20, 30]. The trailing comma in the spelling first, = [7, 8] marked that only the first of the targets was taken, and first became 7. When the right side is short, no error was raised: on the line p1, p2 = 1, p2 became nil. The last three lines are the answer of the defined? probe to different kinds of expression: constant for Math, and method for puts and for String.new. That the constant is classified as constant and the calls as method shows that the defined? probe looks at what the expression looks like.
LB9. That the result of defined?(Math) is constant was measured in this version. In other versions a different word may be returned for the same constant; the word written in the lesson is the output of this version, not a promise of the language.
The Definition Query and the Top-Level Object
defined? looks not only at local variables but at every kind of name. This section tries the probe on a global variable, an instance variable, and a few expressions, and asks what the top-level self object is. The axis is syntax: because defined? is an operator of the language rather than a method, the result concerns what the expression looks like.
# definition_query.rb — the defined? answer for each kind of name d1 = defined?($global) puts d1.inspect $global = 1 puts defined?($global) d2 = defined?(@instance) puts d2.inspect @instance = 2 puts defined?(@instance) puts defined?(self) puts defined?(nil) puts defined?(String) puts defined?(3 + 4) puts self.class puts self.inspect
nil global-variable nil instance-variable self nil constant method Object main
The global variable $global gave nil before it was assigned and "global-variable" after. The instance variable @instance followed the same order: first nil, then "instance-variable". So being defined is a consequence not of the name but of the assignment: a name counts as defined only once a value has been bound to it. defined?(self) gave "self", defined?(nil) gave "nil", and defined?(String) turned out to be "constant". defined?(3 + 4) reported "method": what concerns the probe is that the outermost operation of the expression is a message.
The last two lines asked about the object this whole file runs inside. self.class gave Object and self.inspect gave main. So every expression written without a receiver in a .rb file runs inside an object whose class is Object and whose name is printed as main. The @instance variable assigned at the top level is bound to this object too. The implicit receiver of a receiverless call, for example the puts that was probed with defined? in the previous section, is this main object as well. The syntactic scope determines where names are asked; self determines in which object the question is answered. The probes of this section arrive at the same point: defined? says whether a name is defined and what kind it is, but does not say its value. An undefined name gives nil, and a defined name reports its own kind. These two answers are enough to probe a name before using it. Even though the answer method came for 3 + 4, the one giving the answer is still the defined? operator: the expression was not run, only its form was looked at.
The Bounding Measurement
The responder of this lesson is syntax: the name-binding behavior of the = mark comes from the spelling of the language itself, not from a method defined by a class. Here the slip happens with +=. When y += 1 is written, because there is an addition inside it, one may think it is a message. The measurement asks: are y += 1, y ||= 3, and y + 1 in the same category?
# compound_assignment.rb — += is an assignment, but the addition inside it is a message y = 1 puts defined?(y += 1) puts defined?(y ||= 3) puts defined?(y + 1) puts y puts y.method(:+).owner
assignment assignment method 1 Integer
defined?(y += 1) and defined?(y ||= 3) both gave "assignment", and y + 1 gave "method". Then puts y still printed 1: defined? did not run the assignment. The responder of the addition is Integer, as in the previous lesson. Two jobs are done on the same line: binding the name is the job of the language, that is, syntax; adding two numbers is the job of the class. Only the first is counted as the responder of this lesson, because the lesson is built on name binding.
The common result of the three measurements is this: when a name is written in Ruby, first the sigil of the name is looked at, then the place the sigil points to. The same x = 5, once @ or $ is put in front, is written to another place, but in all three it is the language itself that runs the = mark. For this reason, in later lessons there will be no need to measure assignment separately; when we see = in an expression, we do not look for a message.
Summary
- The mark at the start of a name determines the kind of the name: a lowercase name with no mark is a local variable,
@an instance variable,$a global variable, and an uppercase name a constant. defined?reports the kind of an expression without running it:nilfor an undefined constant,"assignment"for an assignment.- A constant marks the binding of a name but does not freeze the object; an array bound to a constant can change.
- Assignment is not a method call:
defined?gives"assignment"fora = 1and"method"for1 + 2. - The responder of this lesson is syntax:
y += 1andy ||= 3are assignments, and the addition inside is answered byInteger. defined?measures scope too: a method body does not see the outer local (nil), a block does ("local-variable");a, b = b, aswaps the places, andx = y = [1]binds two names to the same object.
Next Step
Name binding was measured as the work of the language. There are other constructs that work with the language’s own spelling: if, while, and case. Of these, who chooses the branches of case; is a when line sending a message to an object? The next lesson, Control Flow, measures this question.
To keep your progress and take notes, Log in
My notes
Log in to take notes.