Ruby Koans: explicit scoping on a class definition part 2
I was just pondering the very same question from the very same koan. I am no expert at scoping, but the following simple explanation made a lot of sense to me, and maybe it will help you as well.
When you define MyAnimals::Oyster
you are still in the global scope, so ruby has no knowledge of the LEGS
value set to 2 in MyAnimals
because you never actually are in the scope of MyAnimals
(a little counterintuitive).
However, things would be different if you were to define Oyster
this way:
class MyAnimals
class Oyster < Animal
def legs_in_oyster
LEGS # => 2
end
end
end
The difference is that in the code above, by the time you define Oyster
, you have dropped into the scope of MyAnimals
, so ruby knows that LEGS
refers to MyAnimals::LEGS
(2) and not Animal::LEGS
(4).
FYI, I got this insight from the following URL (referenced in the question you linked to):
- https://groups.google.com/forum/#!topic/comp.lang.ruby/t5rtDNol3P8
Ruby: explicit scoping on a class definition
I think this example explains it best. Ruby searches for the constant definition in this order:
- The enclosing scope
Any outer scopes (repeat until top level is reached)Any outer scopes (up to but not including the top level- Included modules
- Superclass(es)
- Top level
- Object
- 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
Ruby Koans - Continuation of Lexical Scope vs Inheritance Hierarchy
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.
Why does @Array.clear not work
When running @old_values = @values
, both variables point to the exactly same object. Now, when you assign a new array to @values
as in @values = []
, @values
now is a different array object.
However when running @values.clear
, you are changing the existing array, i.e. the same @old_values
still points to. In this case, when adding new entries to @values
, you are adding them to @old_values
too (as they both point to the same object).
You should read a bit more about how Ruby handles variables and objects. This is part of the first lessons of about all Ruby tutorials.
Related Topics
How to Use Ruby Regexp to Substitute String with a "Callback Function"-Like Manipulation
Ruby Float#Round Method Behaves Incorrectly with Round(2)
How to Generate a Random Number Between a and B in Ruby
How to Run Shell Commands on Server in Capistrano V3
Replacing Text in One CSV Column Using Fastercsv
Message Queues in Ruby on Rails
How to Programmatically Generate Heroku-Like Subdomain Names
How to Use Ruby for Shell Scripting
Rmagick Installation: Can't Find Magickwand.H
Checking If a Variable Is Not Nil and Not Zero in Ruby
Ruby Array to String Conversion
Ruby: Most Concise Way to Use an Env Variable If It Exists, Otherwise Use Default Value
Is Require File.Expand_Path(..., _File_) the Best Practice
How to Reference a File from Inside of a Gem
How to Emit Comments in a Yaml Document Using Psych