24

disclaimer: Code taken from the ruby koans

This is from a discussion of constants scoping within classes. Here is the defintion of a couple few classes:

class Animal
  LEGS = 4
  def legs_in_animal
    LEGS
  end
end

class MyAnimals
  LEGS = 2

  class Bird < Animal
    def legs_in_bird
      LEGS
    end
  end
end

At this point doing MyAnimals::Bird.new.legs_in_bird results in 2 and I understand why--search lexical space for the constant before the inheritance heirarchy.

Then this class is defined:

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

The tutorial says that now calling MyAnimals::Oyster.new.legs_in_oyster results in 4 and I can't figure it out. It appears to me that Oyster is a nested class in MyAnimals and as such I expected it to behave the same ways as the Birds class did above. I'm missing some key information about what declaring the class Oyster with explicit scoping means.

can anyone explain this to me? I've found hundreds of ruby class tutorials via Google but none of them address this situation.

thank you in advance...

jaydel
  • 14,389
  • 14
  • 62
  • 98
  • 3
    There. Are. FOUR. Legs!!! – zetetic Jan 07 '11 at 19:07
  • 1
    Four legs good, two legs bad – Andrew Grimm Feb 17 '11 at 22:30
  • 4
    Does anyone write code that'd depend on which constant would be reached first? – Andrew Grimm Feb 17 '11 at 22:37
  • 1
    Agreed @AndrewGrimm. Just like everybody puts parentheses in complex expressions to make things clear, rather than memorising [this kind of table](http://www.techotopia.com/index.php/Ruby_Operator_Precedence#Operator_Precedence_Table), the real answer here is **"Don't create the kind of situation where you need to know whether the value will come from the scoping or the inheritance."** – Cam Jackson May 08 '14 at 04:49
  • 1
    Right, but I was going through the koans at the time and it was a part of it, not something I wanted to do in the real world :) – jaydel Sep 08 '19 at 18:52

2 Answers2

27

I think this example explains it best. Ruby searches for the constant definition in this order:

  1. The enclosing scope
  2. Any outer scopes (repeat until top level is reached) Any outer scopes (up to but not including the top level
  3. Included modules
  4. Superclass(es)
  5. Top level
  6. Object
  7. Kernel

EDIT

Thanks to Mark Amery for pointing out this error. The top-level is only reached in the case where there are no enclosing scopes and/or superclasses. The linked example actually makes this clear, sadly I read it wrong.

An example for this case:

FOO = 'I pity the foo!'

module One
  FOO = 'one'

  class Two
    FOO = 'two'

    def self.foo
      FOO
    end
  end

  class Three < Two
    def self.foo
      FOO
    end
  end
end

class Four
  class Five < Four
    def self.foo
      FOO
    end
  end
end

describe FOO do
  it "depends where it is defined" do
    expect(FOO).to eq 'I pity the foo!' # top-level
    expect(One::FOO).to eq 'one' # module
    expect(One::Two.foo).to eq 'two' # class
    expect(One::Three.foo).to eq 'one' # outer scope (One) comes before superclass
    expect(Four::Five.foo).to eq 'I pity the foo!' # top-level
  end
end
robosnacks
  • 109
  • 4
zetetic
  • 47,184
  • 10
  • 111
  • 119
  • 1
    What constitutes the 'top level'? If I do `FOO='topoffile'; class Test; FOO='insuperclass'; end; class Test2 < Test; def print_foo; print FOO; end; end; Test2.new.print_foo` (forgive me, you might need to reformat that for readability since comments aren't great for lengthy code snippets) I get `'insuperclass'`, which is definitely coming from the superclass, even though 'topoffile' is available in an outer scope. Something in this answer seems to be wrong, even if it's only for an edge case. – Mark Amery Oct 11 '14 at 17:10
5

If you define the Oyster INSIDE the MyAnimals class definition, then you get the answer that legs_in_oyster is 2.

If you define the Oyster separately--that is, you define it after LEGS = 2 has passed out of scope, you get the response of 4.

This suggests to me that the nested class is behaving differently than a namespace does, perhaps more like a closure.

---EDIT---

irb(main):076:0> class MyAnimals::RunningRoach < Animal; def using_legs; LEGS; end; end
=> nil
irb(main):077:0> MyAnimals::RunningRoach.new.kind_of?(MyAnimals)
=> false
irb(main):078:0> MyAnimals::RunningRoach.new.kind_of?(Animal)
=> true
irb(main):081:0> class MyAnimals::Mantis < MyAnimals; def killing_legs; LEGS; end; end
=> nil
irb(main):082:0> MyAnimals::Mantis.new.kind_of?(Animal)
=> false
irb(main):083:0> MyAnimals::Mantis.new.kind_of?(MyAnimals)
=> true
irb(main):084:0> MyAnimals::Mantis.new.killing_legs
=> 2
irb(main):085:0> MyAnimals::RunningRoach.new.using_legs
=> 4

According to "The Ruby Programming Language", constants are looked up in the Lexical Scope of the place where they are used first, and in the inheritance hierarchy second. So what is the lexical scope of something that inherits Animal? Animal itself, right? The MyAnimals class redefines LEGS, so anything that uses LEGS, and is defined inside MyAnimals, will look for LEGS inside MyAnimals first.

philosodad
  • 1,808
  • 14
  • 24
  • Ah, I was thinking that regardless of where you defined the class, the act of doing MyAnimals:: on it made it a nested class and gave it access to that scope... – jaydel Jan 07 '11 at 18:43