In Progress
Unit 1, Lesson 1
In Progress

Top Level Constant

Video transcript & code

I live in Eastern Tennessee, right next to Great Smoky Mountains National Park. The Smoky Mountains are renowned for the rich variety of wildflowers that bloom in the spring. And since it's prime wildlfower season as I'm making this episode, I thought we could talk about flowers today.

Oh, and also about Ruby constants.

Here's a class named Child. Which subclasses, unsurprisingly enough, a class called Parent

This child, like most children, loves flowers. And particularly loves to pick dandelions.

So if we ask him to name a flower he likes, and he happens to be holding a dandelion, that's what he's likely to say.

FLOWER = "Clover"

module Neighborhood
  FLOWER = "Dogwood"

  module Yard
    FLOWER = "Rose"

    class Parent
      FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      FLOWER = "Dandelion"

      def flower
        FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# => "Dandelion"

But if he didn't have a particular flower in mind…

…he might then look around the yard, and name a flower he saw there. Our yard happens to be full of roses at this time of year.

And indeed, when we execute this code, that's exactly what we see.

FLOWER = "Clover"

module Neighborhood
  FLOWER = "Dogwood"

  module Yard
    FLOWER = "Rose"

    class Parent
      FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# => "Rose"

But supposing you ask him while he's not at home.

If he were still in the neighborhood…

…he might look around at what's blooming, and take note of dogwood flowers.

And that's the answer we see when we run this code.

FLOWER = "Clover"

module Neighborhood
  FLOWER = "Dogwood"

  module Yard
    # FLOWER = "Rose"

    class Parent
      FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# => "Dogwood"

But perhaps you catch him when there are no flowers nearby.

If he can't immediately think of a flower, he might ask one of his parents for a suggestion.

I'm a parent, and personally, I'm partial to the flower of the Mountain Laurel.

And indeed, that's the answer we now get.

FLOWER = "Clover"

module Neighborhood
  # FLOWER = "Dogwood"

  module Yard
    # FLOWER = "Rose"

    class Parent
      FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# => "Mountain Laurel"

Now, if he isn't holding a flower, he can't see any flowers nearby, and he doesn't have one of his parents handy, he might just think of one of the flowers he sees most often in the world around him: the clover.

When we run this code, we see that the answer changes to "Clover".

That's the definition I've set at the top-level, outside of any module or class namespace.

FLOWER = "Clover"

module Neighborhood
  # FLOWER = "Dogwood"

  module Yard
    # FLOWER = "Rose"

    class Parent
      # FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# => "Clover"

What is this "top-level" constant environment, anyway? Is it just some kind of nameless, all-encompassing module?

It turns out that it's not that simple. Because in fact, when we define constants in the top-level evaluation environment, we're really defining them inside the Object class.

Don't believe me? Let's ask the Object class about all of the constants defined within it.

OK, that's a really long list. Let's just search for FLOWER.

Sure enough, there it is. Along with all of Ruby's other standard class constants, we have our FLOWER constant.

FLOWER = "Clover"

Object.constants
# => [:Object, :Module, :Class, :BasicObject, :Kernel, :NilClass, :NIL, :Data...

Object.constants.grep(/FLOWER/)
# => [:FLOWER]

Having top-level constants actually be inside of the Object class makes a weird sort of sense. Because the Ruby top-level is actually evaluated in the context of a special object called main.

And main is an instance of—can you guess?—=Object=.

self                            # => main
self.class                      # => Object

From what we've seen so far, we can start to get a picture of how Ruby looks up constants.

First, Ruby looks for a constant defined in the class of the current object.

If it can't find a definition there, it starts working its way outwards, through containing modules.

When it runs out of surrounding modules, it shifts direction from out to up. It looks for constant definitions in its parent class…

And then in that class' parent, and so on up to the Object class.

So we can sum up Ruby's constant search path very simply as "out, then up".

And that's the end of it, right?

…not quite.

Because Object itself has a superclass: BasicObject.

Object.superclass               # => BasicObject

And in certain cases, we explictly base our objects on BasicObject instead of on Object.

Let's say the Parent class is descended directly from BasicObject.

When we run this code, Ruby can't find a definiton for the FLOWER constant.

Even though there's a top-level defition of it!

FLOWER = "Clover"

module Neighborhood
  # FLOWER = "Dogwood"

  module Yard
    # FLOWER = "Rose"

    class Parent < BasicObject
      # FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# =>

# ~> NameError
# ~> uninitialized constant Neighborhood::Yard::Child::FLOWER
# ~>
# ~> xmptmp-in36362wj.rb:18:in `flower'
# ~> xmptmp-in36362wj.rb:23:in `<main>'

As we just saw a moment ago, Ruby's constant lookup usually ends at the Object class. When we inherit from BasicObject, we put ourselves outside this world of commonly defined constants.

When we run into an error like this, the first thing we might think to do is to explicitly qualify our reference to FLOWER with the Object class, where we know it is defined.

But this too fails!

Why? Because we made reference to the constant Object… which is also defined in the top-level constant scope… which BasicObject doesn't have access to!

FLOWER = "Clover"

module Neighborhood
  # FLOWER = "Dogwood"

  module Yard
    # FLOWER = "Rose"

    class Parent < BasicObject
      # FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        Object::FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# =>

# ~> NameError
# ~> uninitialized constant Neighborhood::Yard::Child::Object
# ~>
# ~> xmptmp-in36364yx.rb:17:in `flower'
# ~> xmptmp-in36364yx.rb:22:in `<main>'

It's a classic catch-22. Fortunately, Ruby provides a way out. By using double-colons at the beginning of a constant name, we force Ruby to look that constant up in the standard top-level constant namespace.

This is known as "fully-qualifying" the constant name.

FLOWER = "Clover"

module Neighborhood
  # FLOWER = "Dogwood"

  module Yard
    # FLOWER = "Rose"

    class Parent < BasicObject
      # FLOWER = "Mountain Laurel"
    end

    class Child < Parent
      # FLOWER = "Dandelion"

      def flower
        ::FLOWER
      end
    end
  end
end
Neighborhood::Yard::Child.new.flower
# => "Clover"

In other words, we force it to look up the constant in the Object class. As a special syntactical rule, this works anywhere, even inside BasicObject-derived objects.

And this is important knowledge to have. Because if you ever find yourself working on BasicObject-inheriting classes, you may find yourself with some very strange errors when you least expect it.

For instance, check out this code.

It uses a pretty typical Ruby idiom to switch on the type of an input object.

And yet, when we run it, we get a NameError.

class Hyperbole < BasicObject
  def exaggerate(obj)
    case obj
    when String then obj.upcase
    when Integer then obj * 2
    end
  end
end

Hyperbole.new.exaggerate(23)
# =>


# ~> NameError
# ~> uninitialized constant Hyperbole::String
# ~>
# ~> xmptmp-in3636nD1.rb:4:in `exaggerate'
# ~> xmptmp-in3636nD1.rb:10:in `<main>'

When we're not expecting it, this type of error can make it seem like Ruby has suddenly lost its mind. What do you mean, you don't recognize the constant String???

The trick, of course, is to fully-qualify the constant references inside of a BasicObject-derived object.

When we run this, everything works fine.

class Hyperbole < BasicObject
  def exaggerate(obj)
    case obj
    when ::String then obj.upcase
    when ::Integer then obj * 2
    end
  end
end

Hyperbole.new.exaggerate(23)
# => 46

And that is how constant lookup works in Ruby, and how it is possible to find oursleves outside the usual world of constant definitions. Happy hacking!

Responses