Skip to content

Everything is an object, even 1 and nil

Level: 101 · a Python programmer's first day in Ruby

One line: Every Ruby value — 1, nil, true, a string, a class, Class itself, even the top-level program — has a class, answers methods and sits on an ancestor chain that ends in BasicObject, and every operator is a named method you can call as 1.+(2).

Python says the same thing about itself, and it is nearly as true there: 1, None and int are all objects, type(type) is type, and 1 + 2 runs int.__add__. The difference is how far Ruby takes it. A literal is a receiver (3.times, "a".upcase, 1.class with no parentheses); an operator is an ordinary method with a name, so 1.+(2) and 1.send(:+, 2) are the very call that 1 + 2 makes; nil is a real object with useful methods (nil.to_a is []); and the program's top level is an object that calls itself main. The program prints each of these as a numbered row, and the Python twin further down prints the same rows in the same order.

Verified output of everything_is_an_object_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

1. every value has a class
   1.class                        Integer
   nil.class                      NilClass
   true.class                     TrueClass
   Integer.class                  Class
   Class.class                    Class
2. an operator is a method call
   1 + 2                          3
   1.+(2)                         3
   1.send(:+, 2)                  3
3. nil is an object with methods
   nil.to_a                       []
   nil.to_s                       ""
   nil.inspect                    "nil"
4. a literal is a receiver
   3.times.to_a                   [0, 1, 2]
   "a".upcase                     "A"
   1.class (no parentheses)       Integer
5. the ancestor chain
   Integer.ancestors              [Integer, Numeric, Comparable, Object, Kernel, BasicObject]
   NilClass.ancestors             [NilClass, Object, Kernel, BasicObject]
6. everything is an Object, classes included
   1.is_a?(Object)                true
   nil.is_a?(Object)              true
   Integer.is_a?(Object)          true
   Integer.is_a?(Class)           true
7. even the top level is an object
   self.inspect                   "main"
   self.class                     Object
8. what a small integer cannot do
   1.singleton_class              TypeError

Reading the output

  • Row 1. 1.class is Integer, nil.class is NilClass, true.class is TrueClass. A class is an object of class Class, and so is Class itself — the chain of "what is the class of that?" closes on itself.
  • Row 2. Three spellings of one call. 1 + 2 is syntax for 1.+(2), and send(:+, 2) names the method with a symbol. That is why operators are methods you can define on your own class.
  • Row 3. nil answers to_a with [] and to_s with "", which is why nil.to_a.each { } and "#{nil}" are safe where a Python None would raise.
  • Row 4. A literal is a receiver. 1.class parses with no parentheses because a . followed by a name after an integer literal starts a method call, not a decimal fraction.
  • Row 5. The chain: Integer inherits from Numeric, mixes in Comparable, then comes Object, the Kernel module, and BasicObject at the root. NilClass goes straight to Object. Kernel is where puts and p live (Not machine-checked here — the BasicObject page measures it.).
  • Row 6. is_a?(Object) is true for a number, for nil and for the class Integer, which is itself a Class.
  • Row 7. self at the top level is an Object that inspects as main, so even a script with no class in it is running inside an object.
  • Row 8. The one thing an Integer cannot do: 1.singleton_class raises TypeError. A small integer has no singleton class, so it cannot carry methods of its own the way a string or an array can — Ruby stores small integers, symbols, nil, true and false as immediate values rather than heap objects (Not machine-checked here.).

Compared with Python

Python's object model is uniform in the same way, and five of the eight rows print the same answer: type(1) is int, type(None) is NoneType, type(int) is type and type(type) is type (row 1); (1).__add__(2) is the call that 1 + 2 makes and getattr(1, "__add__")(2) plays the part of send (row 2); isinstance(int, object) is True (row 6); and an int cannot carry its own attributes either — x.tag = "mine" on an integer is an AttributeError (row 8). Three rows differ. None has no to_a: list(None) is a TypeError, and str(None) is 'None' where nil.to_s is "" (row 3). A literal can be a receiver, but 1.__class__ does not even compile — the tokenizer reads 1. as the start of a float (Not machine-checked here.) — so Python needs (1).__class__, and (3).times is an AttributeError because repeating three times is for _ in range(3), not a method on the number (row 4). The ancestor chain is two long, int then object (row 5), and the top level is a module object named __main__ rather than an instance called main (row 7).

Verified output of everything_is_an_object_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. every value has a class
   type(1).__name__               int
   type(None).__name__            NoneType
   type(True).__name__            bool
   type(int).__name__             type
   type(type).__name__            type
2. an operator is a method call
   1 + 2                          3
   (1).__add__(2)                 3
   getattr(1, "__add__")(2)       3
3. None is an object with methods
   list(None) (no nil.to_a)       TypeError
   str(None)                      'None'
   repr(None)                     'None'
4. a literal is a receiver
   (3).times (no counterpart)     AttributeError
   "a".upper()                    'A'
   1.__class__ (compiled)         SyntaxError
   (1).__class__.__name__         'int'
5. the ancestor chain
   int.__mro__ names              ['int', 'object']
   type(None).__mro__ names       ['NoneType', 'object']
6. everything is an object, classes included
   isinstance(1, object)          True
   isinstance(None, object)       True
   isinstance(int, object)        True
   isinstance(int, type)          True
7. even the top level is an object
   __name__                       '__main__'
   type(sys.modules[__name__])    module
8. what a small integer cannot do
   x = 1; x.tag = 'mine'          AttributeError

The Python library's crosswalk ↗ puts every idea of that library in one table with its spellings; the object model above is what the Ruby column of such a table rests on.

Try it

  1. Write describe(x) that prints the class of x and its ancestors up to and including Object, and call it on 1, nil, "a", [] and Integer. Which of the five chains include a module rather than a class?
  2. 1.method(:+) returns a Method object. Print its owner and arity, then do the same for "a".method(:upcase) and [].method(:each). Finally print method(:puts).owner at the top level — which module does puts come from, and where does that module sit in row 5's chain?
Solution to 1

everything_is_an_object_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Kata: describe(x) prints the class of x and its ancestors up to Object, so a
# reader can see that a literal, nil, a string, an array and a class all sit on
# the same kind of chain.

def describe(x)
  chain = x.class.ancestors.take_while { |m| m != Kernel }
  puts "   #{x.inspect.ljust(8)} #{x.class.to_s.ljust(9)} #{chain.join(' > ')}"
end

puts "value    class     ancestors up to Object"
[1, nil, "a", [], Integer].each { |x| describe(x) }

Verified output of everything_is_an_object_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

value    class     ancestors up to Object
   1        Integer   Integer > Numeric > Comparable > Object
   nil      NilClass  NilClass > Object
   "a"      String    String > Comparable > Object
   []       Array     Array > Enumerable > Object
   Integer  Class     Class > Module > Object

See also