Why are constants declared inside an array, and assigned to other constants, accessible as Class constants in Ruby?

会有一股神秘感。 提交于 2019-12-24 01:16:33

问题


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 constant PARENTS. 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 = :father sets the constant FATHER to :father. The result of this assignment is :father which becomes the first argument.
      • MOTHER = :mother sets the constant MOTHER to :mother. The result of this assignment is :mother which becomes the second argument.

So in chronological order:

  1. The constant FATHER is set to :father
  2. The constant MOTHER is set to :mother
  3. An array with elements :father and :mother is created
  4. The constant PARENTS is 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!