Ruby - Lexical scope vs Inheritance
You can think of each appearance of module Something
, class Something
or def something
as a “gateway” into a new scope. When Ruby is searching for the definition of a name that has been referenced it first looks in the current scope (the method, class or module), and if it isn’t found there it will go back through each containing “gateway” and search the scope there.
In your example the method baz
is defined as
module Foo
class Bar
def baz
puts FOO
end
end
end
So when trying to determine the value of FOO
, first the class Bar
is checked, and since Bar
doesn’t contain a FOO
the search moves up through the “class Bar
gateway” into the Foo
module which is the containing scope. Foo
does contain a constant FOO
(555) so this is the result you see.
The method glorf
is defined as:
class Foo::Bar
def glorf
puts FOO
end
end
Here the “gateway” is class Foo::Bar
, so when FOO
isn’t found inside Bar
the “gateway” passes through the Foo
module and straight into the top level, where there is another FOO
(123) which is what is displayed.
Note how using class Foo::Bar
creates a single “gateway”, skipping over the scope of Foo
, but module Foo; class Bar ...
opens two separate “gateways”
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.
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: 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
Preference of code style in module nesting
These two methods of writing get confused quite often.
First is to say, that to my best knowledge there is no measurable performance difference. (one constant look-up in the below written example)
The most obvious difference, probably the most known, is that your second example (module A::B
) requires for the module A
to exist in the time of the definition.
Otherwise than than that most people think they are interchangeable. That is not true.
Modules are simply constants in ruby and so regular constant look-up applies.
Let me show it on an example:
module A
class Test
end
module B
class Show
p Module.nesting # =>[A::B::Show, A::B, A]
def show_action
Test.respond_with(%q(I'm here!))
end
end
end
end
On the other hand if you call it via the A::B
look what happens:
module A
class Test
end
end
module A::B
class Show
p Module.nesting # => [A::B::Show, A::B]
def show_action
Test.respond_with(%q(I'm here!))
end
end
end
The difference is that .nesting
produces:
1) in the first case: [A::B::Show, A::B, A]
(you are nested in module A
)
2) in the second case: [A::B::Show, A::B]
(here not)
Module methods in Ruby
This is how I see it:
Dollar::line
There is no such method defined in this module so It's calling At::line
because you included this module.
Star::line
It uses last defining from Dollar
module(it goes after original Star
definition so it's overridden).
Dollar::line
Third call is the same as the first one.
line
And the last one is At::line
because You made an include.
When is Lexical Scope for a function within a function determined?
When f
is run, the first thing that happens is that a function g
is created in f
's local environment. Next, the variable z
is created by assignment.
Finally, x
is added to the result of g(x)
and returned. At the point that g(x)
is called, x = 3
and g
exists in f
's local environment. When the free variable z
is encountered while executing g(x)
, R looks in the next environment up, the calling environment, which is f
's local environment. It finds z
there and proceeds, returning 7. It then adds this to x
which is 3.
(Since this answer is attracting more attention, I should add that my language was a bit loose when talking about what x
"equals" at various points that probably do not accurately reflect R's delayed evaluation of arguments. x
will be equal to 3 once the value is needed.)
Using :: instead of module ... for Ruby namespacing
If you use class Foo::Bar
, but the Foo
module hasn't been defined yet, an exception will be raised, whereas the module Foo; class Bar
method will define Foo
if it hasn't been defined yet.
Also, with the block format, you could define multiple classes within:
module Foo
class Bar; end
class Baz; end
end
Related Topics
Rails - Sort by Join Table Data
Ruby: Dynamically Generate Attribute_Accessor
Ruby on Rails - "Add 'Gem SQLite3'' to Your Gemfile"
How to Save an Object to a File
How to Remove Permission Denied @ Rb_Sysopen - Gem Install Error
How to Uninstall Ruby on Rails on MAC Os X
Validate Words Against an English Dictionary in Rails
Set Attribute Dynamically of Ruby Object
Why Does Array.Slice Behave Differently for (Length, N)
Is There an Expect Equivalent Gem for Ruby
Prevent Rails Test from Deleting Seed Data
Running into Smtp Error When Trying to Send Email in Ror App
Problems Installing Ruby on Mountain Lion - Ruby 1.9.3 Wont' Compile
Missing Symbol When Installing Ruby-2.3.0 on Os X 10.11.6 by Rvm