Skip to content
academia.sh

Lesson 02 / 12

An Intuition for the Object Model

Numbers, nil, and true are objects too; in 5 + 3 the receiver is 5 and the responder is Integer; classes are objects themselves, and another class answers the messages sent to them.

Published Updated

In this lesson

  • Shows by running code that values such as numbers, nil, and true are objects belonging to a class
  • Separates the receiver from the responder in a message and finds the responder in 5 + 3 with an owner measurement
  • Measures that classes are objects too and that another class answers the message sent to them
Contents

The previous lesson established that every operation is a message sent to a receiver, and showed within the twelve observations that the + of 5 + 3 is defined in the Integer class. That sentence assumed something: 5 is an object and belongs to a class. This lesson measures that assumption. Primitive types were measured in the JavaScript Fundamentals course (M06/K01); in Ruby the same question is asked in a different form: if a message can be sent to a value, who handles that message?

Object orientation was established in M08 with Java, and the concepts of object and class were defined there. What is new here is that in Ruby numbers, nil, and truth values are inside the same model. In addition, this lesson takes a single row from the core: integer addition.

Every Value Is an Object

Ruby has no separate category of primitive values. The file below sends the same message, class, to eight different values. On each line the representation of the value and its class are printed side by side.

# values.rb — every value has a class
values = [5, 3.5, "name", :name, nil, true, [1], 1..2]

values.each do |value|
  printf("%-6s %s\n", value.inspect, value.class)
end
5      Integer
3.5    Float
"name" String
:name  Symbol
nil    NilClass
true   TrueClass
[1]    Array
1..2   Range

The integer, the floating-point number, the string, the symbol, nil, the truth value, the array, and the range all come from a class. nil and true are the single instance of a class: NilClass and TrueClass. This means messages can be sent to values. The file below sends one message each to a number, to true, and to nil, and asks the owner of each method.

# primitive_messages.rb — numbers, true, and nil answer messages
puts 5.times.class
puts 5.method(:times).owner
puts true.method(:&).owner
puts nil.method(:to_a).owner
p nil.to_a
p true & false
Enumerator
Integer
TrueClass
NilClass
[]
false

5.times returns an enumerator, and its owner is Integer. true & false looks like an operator but is a method defined in TrueClass. nil.to_a gives the empty array, and its owner is NilClass. All three messages found their answer in the receiver’s own class.

LB5. In this lesson, an “object” is any value a message can be sent to. How a value is held in memory is not measured; only the behavior is measured, that is, who responds to which message. For this reason object_id and inspect output that carries a memory address are not printed.

Receiver and Responder

In the expression 5 + 3, the receiver is 5, the message is +, and the argument is 3. M11/K04 measured which object is the target of an operation. Here the receiver is known; the question is which definition answers the message that reaches the receiver. In 5 + 3 the receiver is 5 and the responder is Integer.

# receiver_responder.rb — in 5 + 3 the receiver and the responder are different things
receiver = 5

puts defined?(receiver + 3)
puts receiver.method(:+).owner
puts receiver.method(:+).owner.instance_of?(Class)
puts receiver + 3
method
Integer
true
8

defined? returned "method" for the expression: there really is a message here. The owner of the method is Integer, and Integer is an instance of Class; in this case the classification rule of the course counts the responder as a class. The receiver is a value, the responder is a definition. The two often do not carry the same name: 5 is a value, and Integer is the class it belongs to.

This line is the integer addition observation in the shared core of the course, and, as in the whole core, its responder is a class. This lesson does not pull in reply.rb; the defined? result and the owner of the method are printed directly. The rule is the same: first defined?, then the owner.

Classes Are Objects Too

Since every value has a class, Integer must have a class too. In Ruby Integer is a constant and points to an object; that object can answer a message as well. But who answers when a message is sent to Integer? The file below prints the class of 5 and of Integer, and then the owner of the === message sent to Integer.

# class_objects.rb — classes are objects, and another class answers their messages
puts 5.class
puts Integer.class
puts Integer.method(:===).owner
puts Integer.method(:===).owner.instance_of?(Class)
puts Integer === 5
puts Integer === "5"
Integer
Class
Module
true
true
false

The class of 5 is Integer; the class of Integer is Class. So Integer is an object in its own right, and the === message sent to it is answered not by Integer but by Module. Module is a class too, so the rule again counts the responder as a class. Integer === 5 returns true and Integer === "5" returns false: here === asks “does this value belong to this class”.

A Message Depends on the Receiver’s Class

That every value has a class means that not every value can answer every message. The times message is defined in Integer; it is not in NilClass, the class of nil. The file below asks three messages of two receivers, and looks at what happens when times is sent to nil.

# message_probe.rb — a message depends on the receiver's class
[5, nil].each do |receiver|
  printf("%-4s times: %-5s to_a: %-5s to_s: %s\n", receiver.inspect, receiver.respond_to?(:times), receiver.respond_to?(:to_a), receiver.respond_to?(:to_s))
end

begin
  nil.times
rescue NoMethodError => e
  puts e.class
end
5    times: true  to_a: false to_s: true
nil  times: false to_a: true  to_s: true
NoMethodError

respond_to? asks whether an object answers a message. 5 answers the times and to_s messages; nil answers to_a and to_s but does not answer times. Sending a message that is not answered raises NoMethodError, and here only the class of the exception was printed. So the sentence “everything is an object” does not mean “every object accepts every message”: the counterpart of a message must be found in the receiver’s class or in the definitions above that class.

Probing That Everything Is an Object

The previous sections built the object model through the receiver, the responder, and the class. This section tests the claim “everything is an object” one by one, on numbers, nil, and the classes themselves. The limit of the claim is also measured: the two queries that ask what kind of thing a value is, instance_of? and is_a?, do not ask the same question. The axis is again the responder; the owner of one of the queries is read on the last line of the output.

# everything_object.rb — there is no value that is not an object
puts 5.respond_to?(:+)
puts nil.respond_to?(:to_a)
puts 5.frozen?
puts 5.methods.size > 50
puts Integer.class
puts Class.class
puts 5.instance_of?(Integer)
puts 5.is_a?(Comparable)
puts 5.instance_of?(Comparable)
puts 5.method(:frozen?).owner
true
true
true
true
Class
Class
true
true
false
Kernel

The first two lines gave true: 5 answers the addition message, and nil answers the to_a message. 5.frozen? also returned true; a number is an immutable object. The question 5.methods.size > 50 was true. Here we deliberately did not print the number itself: how many methods an object has changes with the version, but the result that it has quite a few does not. The fifth and sixth lines are the real finding of the section: Integer.class and Class.class both printed Class. Integer is a class and an object; the class of that object is Class. Class itself is an object too, and its class is also Class. The chain closes on itself, so the question “what is the class of a class” does not go on forever.

The last three queries show the difference between two methods. 5.instance_of?(Integer) gave true, 5.is_a?(Comparable) gave true, and 5.instance_of?(Comparable) gave false. instance_of? asks only about the object’s direct class; is_a? asks the broader question of whether the object counts as being of that kind. We do not open up how the chain works here; we only record the difference. The owner on the last line turned out to be Kernel: the 5.frozen? message is answered not by the number’s class but by a module that every object takes in common.

LB6. The > 50 threshold is a choice: the measurement only shows that the number is above the threshold, and the number itself is not a promise of the language.

# nil_object.rb — nil is an object too and has an owner
puts nil.class
puts nil.to_a.inspect
puts nil.to_s.empty?
puts nil.method(:to_a).owner
puts true.class
puts true.method(:&).owner
NilClass
[]
true
NilClass
TrueClass
TrueClass

nil is an object too: nil.class gave NilClass. nil.to_a returned an empty array, and because nil.to_s gives an empty string, the question empty? was true. So even the “no value” state responds to messages, and this is why calling to_a on nil produces [] rather than an error. nil.method(:to_a).owner turned out to be NilClass: the responder is nil’s own class. The same pattern holds for true: its class is TrueClass, and the owner of the & message is TrueClass. The classes of true and nil are separate, and the owners of the two measured messages turned out to be those two classes themselves. The common conclusion of these two sections tightens the intuition at the start of the lesson: in Ruby no value is closed to messages. Whatever the value of an expression is, a message can be written after it, and the responder can be found as a class or a module.

Chained Messages and the Return Object

Every message returns an object, and the returned object becomes the receiver of the next message. This section measures that at each link of the chain the responder can change. The expression measured is a chain such as 5.to_s.size.to_f; the chain has to be read from the left: first 5 receives the message, the object that comes out receives the next one, and so it goes on. The axis is again the owner: the owners of two links are printed separately.

# chain.rb — every message returns an object and the chain goes on
result = 5.to_s.size.to_f
puts result
puts result.class
puts 5.to_s.class
puts 5.to_s.size.class
puts [3, 1, 2].sort.reverse.first
puts "ruby".reverse.upcase.class
puts 5.method(:to_s).owner
puts 5.to_s.method(:size).owner
1.0
Float
String
Integer
3
String
Integer
String

The result of the chain was 1.0, and its class is Float. Probing the intermediate links one by one shows how the chain is built: 5.to_s gave a String, and 5.to_s.size gave an Integer. So within the same expression there are four separate objects and three separate classes: Integer for 5, String for "5", Integer again for 1, and finally Float for 1.0. [3, 1, 2].sort.reverse.first went down to an element after two array links and gave 3; "ruby".reverse.upcase.class printed String, because both links return strings.

The owner lines showed that two different links are defined in two different classes: 5.method(:to_s).owner turned out to be Integer, and 5.to_s.method(:size).owner turned out to be String. The same names to_s and size could have gone to a different owner for another receiver; the receiver’s class determines the responder, not the position in the chain. This is the concrete counterpart of the claim “Ruby is object-oriented”: an operation is sending a message to an object, and the answer is again an object. To read a chain, one has to know the class of the return object of each link; when the class changes, the next message goes to a different responder. So when an error occurs in a long chain, the way to find where it arose is to split the chain link by link and probe the class of each intermediate object.

Equality and Identity Are Separate Questions

Equality and identity are two separate questions. == asks whether two objects are equal in value, and equal? asks whether the two are the same object. This section places the two queries side by side on the same values. The axis is the owner: the place where the method answering the two queries is defined is printed at the end.

# identity.rb — equality and identity are separate questions
a = "x"
b = a
puts a.equal?(b)
puts a.equal?("x")
puts a == "x"
puts a.dup.equal?(a)
puts 1.equal?(1)
puts :s.equal?(:s)
puts nil.equal?(nil)
puts 1 == 1.0
puts 1.eql?(1.0)
puts 1.equal?(1.0)
puts a.method(:==).owner
puts a.method(:equal?).owner
true
false
true
false
true
true
true
true
false
false
String
BasicObject

After the assignment b = a, a.equal?(b) gave true: two names point to a single object. a.equal?("x") was false and a == "x" was true: the text is the same, the object is different. a.dup.equal?(a) also gave false, because dup produces a new object. A number, a symbol, and nil behaved differently: 1.equal?(1), :s.equal?(:s), and nil.equal?(nil) all three gave true; every spelling of these values is the same object.

In comparisons among numbers, the three queries gave two different answers. 1 == 1.0 was true, while 1.eql?(1.0) and 1.equal?(1.0) gave false. So == looks at values and sees equality between an integer and a floating-point number; eql? and equal? ask stricter questions. The owner lines showed where the two queries are defined: String for == and BasicObject for equal?. So == is a question left to each class’s own definition, while equal? is defined in a single common place. For this reason, before asking whether a value is “the same”, one has to state in which sense it is asked. In conclusion, equal? is used for the identity question and == for the value question; confusing the two opens the way to taking two separate objects for the same one or the same object for two.

The Bounding Measurement

In most of this lesson’s measurements the responder is a class; Kernel, a module, is the exception for class and frozen?, and BasicObject for equal?. The expression “the class responder” can slip toward two different things. The slip goes like this: one may assume that the responder of the Integer.=== message is Integer. The run above showed this is wrong. The responder is not the receiver itself but the chain of classes the receiver belongs to: if the receiver is 5, it is Integer; if the receiver is Integer, it is Module in the chain of Class.

# receivers_class.rb — the responder is the class the receiver belongs to, not the receiver itself
[5, "name", Integer, String].each do |receiver|
  printf("%-8s class %-8s method owner %s\n", receiver.inspect, receiver.class, receiver.method(:class).owner)
end
5        class Integer  method owner Kernel
"name"   class String   method owner Kernel
Integer  class Class    method owner Kernel
String   class Class    method owner Kernel

The owner of the class message is Kernel for all four receivers, because this message is defined in a module common to every object. The classes of the receivers, however, differ. The question “who answers this message” has two layers: which class the receiver belongs to, and whether that class answers the message itself. What was measured in this lesson is the first layer. The measurement of the second, the cases where the method is found not in the receiver’s class but in a definition above it, continues in the Methods lesson, whose subject is the module responder.

Summary

  • In Ruby, numbers, nil, true, strings, symbols, arrays, and ranges are all objects; each has a class.
  • In 5 + 3 the receiver is 5 and the responder is Integer; the receiver is a value, the responder is a definition.
  • The messages 5.times, true & false, and nil.to_a are answered by Integer, TrueClass, and NilClass respectively.
  • Classes are objects too: Integer.class is Class, and the Integer.=== message is answered by Module.
  • The responder of this lesson is mostly a class: the message is answered not by the receiver itself but by the class the receiver belongs to. The owner of messages common to every object, such as class and frozen?, is the Kernel module.
  • Integer.class and Class.class both give Class; equal? asks identity and == asks value, and the responder of each link in a chain is the class of that link’s receiver.

Next Step

It was seen that objects have a class and that messages are sent to them. But how is a name bound to an object, and who is at work when x = 5 is written? Whether assignment is a message, the naming rules, and the sigils are the subject of the next lesson, Variables and Constants.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close