Question

I've had a chance to look around in StackOverflow and found this same question which I was trying to better understand from Ruby Koans (Ruby Koans: explicit scoping on a class definition part 2).

class MyAnimals
  LEGS = 2

  class Bird < Animal
    def legs_in_bird
      LEGS
    end
  end
end

def test_who_wins_with_both_nested_and_inherited_constants
  assert_equal 2, MyAnimals::Bird.new.legs_in_bird
end

# QUESTION: Which has precedence: The constant in the lexical scope,
# or the constant from the inheritance heirarachy?

# ------------------------------------------------------------------

class MyAnimals::Oyster < Animal
  def legs_in_oyster
    LEGS
  end
end

def test_who_wins_with_explicit_scoping_on_class_definition
  assert_equal 4, MyAnimals::Oyster.new.legs_in_oyster
end

# QUESTION: Now Which has precedence: The constant in the lexical
# scope, or the constant from the inheritance heirarachy?  Why is it
# different than the previous answer?

Based on the explanation in the link, it seems like the main confusion others (including myself) had was because of the class definition:

class MyAnimals::Oyster < Animal
  # stuff goes in here
end

My original thought was that MyAnimals::Oyster means that the Oyster class was defined within MyAnimals. In other words, I thought the above code was analogous to the following code:

class MyAnimals
  class Oyster < Animal
    # stuff goes in here
  end
end

To test my thought, I did the following in IRB:

class MyAnimals
  LEGS = 2

  class Bird < Animal
    def legs_in_bird
      LEGS
    end
  end
end

class MyAnimals::Oyster # You should notice that I'm not inheriting from Animal anymore
  def legs_in_oyster
    LEGS
  end
end

If my reasoning is correct, then I would expect that the below code returns 2

MyAnimals::Oyster.new.legs_in_oyster # => NameError: uninitialized constant MyAnimals::Oyster::LEGS

Since this doesn't return 2, can someone explain to me why it doesn't return 2?

EDIT: I neglected to add the Animal class; here it is:

class Animal
  LEGS = 4
  def legs_in_animal
    LEGS
  end

  class NestedAnimal
    def legs_in_nested_animal
      LEGS
    end
  end
end
Was it helpful?

Solution 2

In strictly lexically/statically scoped languages like C++ or Java, identifiers are resolved by simply checking the current scope, ascending one scope higher and repeating until the base most scope is reached. For example: if your example were C++, LEGS would be searched for first in the calling class Bird/Oyster, then Animal, then My Animal.

Ruby has a kind of dynamic scoping. Each object, even if it resides in the same 'place' can have its own scope lookup order depending on how it was defined or create at runtime. You can think of each method as having a stack of scopes to search, and how it was defined pushes new scopes onto that stack.

Because of the way Bird is defined it gets (BaseScope is not its real name, you did not provide enough code to provide this) BaseScope::MyAnimals::Bird, BaseScope::MyAnimals::Animal, BaseScope::MyAnimals and BaseScope to search through for resolving names.

While The first Oyster only gets BaseScope::MyAnimals::Oyster, BaseScope::MyAnimals::Animal and BaseScope.

The Oyster without inheritance gets even less, just BaseScope::MyAnimals::Oyster and BaseScope.

Each use of the class keyword and inheritance in this example pushes another scope to check onto the stack of scopes for its contents to search. So using class MyAnimals::Oyster only pushed one entry onto this stack.

Edit

For simplicity I left out the method legs_in_oyster. It is a scope that could be searched. It's trivial definition is self explanatory and including it would add much useless text to this answer.

I also left out the global scope for simplicity. I know Koans has at least one scope at or between BaseScope and the global scope.

OTHER TIPS

Go back to the order in which Ruby searches for the value. Step 1 is 'the enclosing scope'. When you define a nested class like this

class Animal
    class Bird
    end
end

'Bird' is enclosed in the 'Animal' scope.

When you do it like this

class Animal
end

class Animal::Bird
end

Although you've defined 'Bird' as nested within 'Animal', at the time you defined 'Bird', the enclosing scope was the global scope. Go back to IRB and define LEGS = 0 (in the global scope) and try your Oyster again.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top