问题
Given the following Ruby class
class Example
PARENTS = [
FATHER = :father,
MOTHER = :mother
]
end
These work as expected
> Example::PARENTS
#=> [:father, :mother]
> Example::PARENTS[0]
#=> :father
> Example::PARENTS[1]
#=> :mother
However, why does this work?
> Example::FATHER
#=> :father
> Example::MOTHER
#=> :mother
In fact, why are there three constants in the Example class's scope?
> Example.constants
#=> [:MOTHER, :PARENTS, :FATHER]
To the point, that if I extend the class with an additional method:
class Example
def self.whos_your_daddy
FATHER
end
end
It accesses the constant like normally.
> Example.whos_your_daddy
#=> :father
How is this behavior possible? By declaring the constants inside of an array, I would expect them to to be scoped inside the array. Please cite the relevant docs in your answer.
Edit: I suppose I'll clarify, the easiest way to answer this question is to explain two things:
First, what happens when the following code is executed:
PARENTS = [
FATHER = :father,
MOTHER = :mother
]
Second, does declare a constant anywhere tie it to the scope of the class it is being declared in? Why?
回答1:
First, what happens when the following code is executed:
PARENTS = [ FATHER = :father, MOTHER = :mother ]
PARENTS = ...attempts to set the constantPARENTS. But in order to do so, it has to evaluate the right-hand side of the assignment:[...]attempts to create an array. But in order to do so, it has to evaluate its arguments:FATHER = :fathersets the constantFATHERto:father. The result of this assignment is:fatherwhich becomes the first argument.MOTHER = :mothersets the constantMOTHERto:mother. The result of this assignment is:motherwhich becomes the second argument.
So in chronological order:
- The constant
FATHERis set to:father - The constant
MOTHERis set to:mother - An array with elements
:fatherand:motheris created - The constant
PARENTSis set to that array
Your code is equivalent to:
FATHER = :father
MOTHER = :mother
PARENTS = [FATHER, MOTHER] # or [:father, :mother]
By declaring the constants inside of an array, I would expect them to to be scoped inside the array. Please cite the relevant docs in your answer.
You can use Module.nesting to determine the current nesting, i.e. where a constant will be defined in: (more examples in the docs)
class Example
p outer_nesting: Module.nesting
PARENTS = [
p(inner_nesting: Module.nesting)
]
end
Output:
{:outer_nesting=>[Example]}
{:inner_nesting=>[Example]}
As you can see, an array literal doesn't affect the current nesting. Constants in both locations will be defined under Example.
If you really wanted to declare the constants "inside" the array (i.e. inside the array's singleton class), you could do some like this:
class Example
PARENTS = []
class << PARENTS
FATHER = :father
MOTHER = :mother
PARENTS.push(FATHER, MOTHER)
end
end
p Example.constants #=> [:PARENTS]
p Example::PARENTS.singleton_class.constants #=> [:FATHER, :MOTHER]
The above is just for demonstration purposes, there's no need to actually do that.
回答2:
I can understand how it can be confusing, but apart from the fact that reassigning values to constants is discouraged, in terms of scope — instance variables and constants are extremely similar.
The visual trick in the first class declaration is where you declare the constant inside an array.
First: understand that when you declare a constant, the returned value is your definition. E.g.
FATHER = :father
#=> :father
Now, let's look at the constant declaration:
PARENTS = [
FATHER = :father,
MOTHER = :mother
]
For declaring PARENT, you could have just used:
PARENTS = [
:father,
:mother
]
but you went a step further and declared a constant inside the definition as well. Now please understand that the scope of instance variables and constants is similar, it is tied to the instance it was declared in, and so declaring a constant anywhere would tie it to the instance.
By executing FATHER = :father, you've declared another constant, and the scope of a constant is always going to be the class it was declared in, in this case Example. The same is also true for MOTHER = :mother.
If you're more used to instance variables — it's the same reason this works: Given the following Ruby class.
class Example
@parents = [
@father = :father,
@mother = :mother
]
end
These work as expected
> Example.instance_variable_get :@parents
#=> [:father, :mother]
> Example.instance_variable_get(:@parents)[0]
#=> :father
> Example.instance_variable_get(:@parents)[1]
#=> :mother
But this works too.
> Example.instance_variable_get :@father
#=> :father
> Example.instance_variable_get :@mother
#=> :mother
In fact, these three are in the Example class's scope.
> Example.instance_variables
#=> [:@mother, :@parents, :@father]
To the point, that if I extend the class with an additional method:
class Example
def self.whos_your_daddy
@father
end
end
It accesses the instance variables like normally.
> Example.whos_your_daddy
#=> :father
回答3:
I found this in the ruby programming book, page 94:
Constants defined within a class or module may be accessed unadorned anywhere within the class or module. Outside the class or module, they may be accessed using the scope operator, ::, prefixed by an expression that returns the appropriate class or module object. Constants defined outside any class or module may be accessed unadorned or by using the scope operator with no prefix. Constants may not be defined in methods. Constants may be added to existing classes and modules from the outside by using the class or module name and the scope operator before the constant name.
Conclusion, there cannot be two constants with the same name within a class, one inside the array and one outside. So you do not need the scope to access it, since the scope is the whole class.
You will not need a constant within a constant array, since the array being constant its internal values are constant too.
回答4:
A short answer could be that:
- Constants can only be stored inside Modules *. const_set is defined for Module, not Object.
- Classes are Modules.
- An Array instance isn't a Module.
Your code is equivalent to:
class Example
PARENTS = [
Example.const_set("FATHER", :father),
Example.const_set("MOTHER", :mother)
]
end
with some tests:
puts Example.is_a? Module
# true
puts Example.is_a? Class
# true
p Example::PARENTS
# [:father, :mother]
p Example.constants
# [:FATHER, :MOTHER, :PARENTS]
puts Example::PARENTS.is_a? Module
# false
Example::PARENTS.const_set("UNCLE", :uncle)
# undefined method `const_set' for [:father, :mother]:Array (NoMethodError)
*: Top-level constants (= constants defined outside a class or module) seem to be stored in Object.
回答5:
Your answer is in your question.
class Example
PARENTS = [
FATHER = :father,
MOTHER = :mother
]
end
Here 3 constants (PARENTS, FATHER and MOTHER). And they in one scope. Array doesn't make new scope.
And Example.constants method just shows you them.
Even if you add your method to your class it changes absolutely nothing
class Example
PARENTS = [
FATHER = :father,
MOTHER = :mother
]
def self.whos_your_daddy
FATHER
end
end
Example.constants #=> [:MOTHER, :PARENTS, :FATHER]
回答6:
Array definition is not a block. It does not run in Array's scope.
It's no different from:
PARENTS = []
PARENTS << mom = :mother
As you can see, scope does not change:
> puts self
main
=> nil
> array = [ puts(self.inspect) ]
main
=> [nil]
Assignment returns the assigned value:
> foo = "bar"
"bar"
> puts foo
bar
> puts(foo = "baz")
baz
> puts foo
baz
You can't "store" something like MOTHER = :mother in an Array as it is not a value, it returns a value, which is :mother.
来源:https://stackoverflow.com/questions/54526667/why-are-constants-declared-inside-an-array-and-assigned-to-other-constants-acc