Почему константы, объявленные внутри массива и назначенные другим константам, доступны как константы класса в Ruby? - PullRequest
0 голосов
/ 05 февраля 2019

Учитывая следующий класс Ruby

class Example
  PARENTS = [
    FATHER = :father,
    MOTHER = :mother
  ]
end

Они работают как ожидалось

> Example::PARENTS
#=> [:father, :mother]
> Example::PARENTS[0]
#=> :father
> Example::PARENTS[1]
#=> :mother

Однако, почему это работает?

> Example::FATHER
#=> :father
> Example::MOTHER
#=> :mother

На самом деле, почемуСуществуют ли три константы в области действия класса Example?

> Example.constants
#=> [:MOTHER, :PARENTS, :FATHER]

В том смысле, что если я расширяю класс дополнительным методом:

class Example
  def self.whos_your_daddy
    FATHER
  end
end

Он получает доступ к константе, как обычно.

> Example.whos_your_daddy
#=> :father

Как такое поведение возможно?Объявляя константы внутри массива, я ожидаю, что они будут ограничены внутри массива.Пожалуйста, укажите соответствующие документы в своем ответе.

Редактировать: Полагаю, я уточню, самый простой способ ответить на этот вопрос - объяснить две вещи:

СначалаЧто происходит, когда выполняется следующий код:

PARENTS = [
  FATHER = :father,
  MOTHER = :mother
]

Во-вторых, объявляет ли константа где-нибудь ее привязку к области действия класса, в котором она объявлена?Почему?

Ответы [ 6 ]

0 голосов
/ 05 февраля 2019

Во-первых, что происходит, когда выполняется следующий код:

PARENTS = [
  FATHER = :father,
  MOTHER = :mother
]
  • PARENTS = ... пытается установить константу PARENTS.Но для этого он должен оценить правую часть присваивания:
    • [...] пытается создать массив.Но для этого он должен оценить свои аргументы:
      • FATHER = :father устанавливает постоянную FATHER в :father.Результатом этого присваивания является :father, который становится первым аргументом.
      • MOTHER = :mother устанавливает константу MOTHER в :mother.Результат этого присваивания равен :mother, который становится вторым аргументом.

Таким образом, в хронологическом порядке :

  1. Константа FATHER установлена ​​в :father
  2. Константа MOTHER установлена ​​в :mother
  3. Массив с элементами :father и :mother создан
  4. Для этого массива установлена ​​константа PARENTS

Ваш код эквивалентен:

FATHER = :father
MOTHER = :mother
PARENTS = [FATHER, MOTHER]  # or [:father, :mother]

Объявляя константывнутри массива, я ожидал бы, что они будут ограничены внутри массива.Пожалуйста, укажите соответствующие документы в своем ответе.

Вы можете использовать Module.nesting для определения текущей вложенности, т. Е. Где константа будет определена в: (больше примеров в документах )

class Example
  p outer_nesting: Module.nesting
  PARENTS = [
    p(inner_nesting: Module.nesting)
  ]
end

Вывод:

{:outer_nesting=>[Example]}
{:inner_nesting=>[Example]}

Как видите, литерал массива не влияет на текущую вложенность.Константы в обоих местах будут определены в Example.

Если вы действительно хотите объявить константы «внутри» массива (то есть внутри синглтон-класса массива), вы можете сделать что-то вроде этого:

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]

Выше приведено только для демонстрационных целей, в этом нет необходимости.

0 голосов
/ 05 февраля 2019

Короткий ответ может быть следующим:

Ваш код эквивалентен:

class Example
  PARENTS = [
    Example.const_set("FATHER", :father),
    Example.const_set("MOTHER", :mother)
  ] 
end

с некоторыми тестами:

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)

*: Константы верхнего уровня (= константы, определенные вне класса или модуля), похоже, хранятся в Object.

0 голосов
/ 05 февраля 2019

Определение массива не является блоком.Он не работает в области видимости массива.

Он ничем не отличается от:

PARENTS = []
PARENTS << mom = :mother

Как видите, область действия не меняется:

> puts self
main
=> nil
> array = [ puts(self.inspect) ]
main
=> [nil]

Назначение возвращаетприсвоенное значение:

> foo = "bar"
"bar"
> puts foo
bar
> puts(foo = "baz")
baz
> puts foo
baz

Вы не можете «сохранить» что-то вроде MOTHER = :mother в массиве, так как это не значение, оно возвращает значение, равное :mother.

0 голосов
/ 05 февраля 2019

Я нашел это в книге по программированию ruby, стр. 94:

К константам, определенным в классе или модуле, можно получить доступ без надписей в любом месте класса или модуля.Вне класса или модуля к ним можно получить доступ, используя оператор области действия :: с префиксом выражения, которое возвращает соответствующий объект класса или модуля.К константам, определенным вне любого класса или модуля, можно получить доступ без надписей или с помощью оператора области действия без префикса.Константы не могут быть определены в методах.Константы могут быть добавлены к существующим классам и модулям извне с помощью имени класса или модуля и оператора области действия перед именем константы.

Вывод: в константе не может быть двух констант с одинаковым именемкласс, один внутри массива и один снаружи.Таким образом, вам не нужна область видимости для доступа к нему, поскольку область видимости является целым классом.

Вам не понадобится константа в константном массиве, поскольку массив, являющийся константой, его внутренние значения также постоянны.

0 голосов
/ 05 февраля 2019

Ваш ответ на ваш вопрос.

class Example
  PARENTS = [
    FATHER = :father,
    MOTHER = :mother
  ]
end

Здесь 3 константы (PARENTS, FATHER и MOTHER).И они в одной области.Массив не создает новую область видимости.

И метод Example.constants просто показывает их.

Даже если вы добавите свой метод в свой класс, он абсолютно ничего не изменит

class Example
  PARENTS = [
    FATHER = :father,
    MOTHER = :mother
  ]

  def self.whos_your_daddy
    FATHER
  end
end

Example.constants #=> [:MOTHER, :PARENTS, :FATHER]
0 голосов
/ 05 февраля 2019

Я могу понять, как это может сбивать с толку, но кроме того факта, что переназначение значений константам не рекомендуется, с точки зрения области видимости - переменные экземпляра и константы чрезвычайно похожи.

Визуальный прием в первомОбъявление класса - это то, где вы объявляете константу внутри массива.

Первое: поймите, что когда вы объявляете константу, возвращаемое значение является вашим определением.Например,

FATHER = :father
#=> :father

Теперь давайте посмотрим на объявление константы:

PARENTS = [
  FATHER = :father,
  MOTHER = :mother
]

Для объявления PARENT вы могли бы просто использовать:

PARENTS = [
  :father,
  :mother
]

, ноВы пошли дальше и объявили константу внутри определения.Теперь, пожалуйста, поймите, что область действия переменных и констант экземпляра аналогична , она привязана к экземпляру, в котором она была объявлена, и поэтому объявление константы в любом месте связало бы ее с экземпляром.

Выполнив FATHER = :father, вы объявили другую константу, и областью действия константы всегда будет класс, в котором она была объявлена, в данном случае Example.То же самое верно и для MOTHER = :mother.

Если вы более привыкли к переменным экземпляра - это та же самая причина, по которой это работает: учитывая следующий класс Ruby.

class Example
  @parents = [
    @father = :father,
    @mother = :mother
  ]
end

Эти работыкак и ожидалось

> Example.instance_variable_get :@parents
#=> [:father, :mother]
> Example.instance_variable_get(:@parents)[0]
#=> :father
> Example.instance_variable_get(:@parents)[1]
#=> :mother

Но это тоже работает.

> Example.instance_variable_get :@father
#=> :father
> Example.instance_variable_get :@mother
#=> :mother

Фактически, эти три находятся в области действия класса Example.

> Example.instance_variables
#=> [:@mother, :@parents, :@father]

Кстати,что если я расширю класс дополнительным методом:

class Example
  def self.whos_your_daddy
    @father
  end
end

Он обращается к переменным экземпляра, как обычно.

> Example.whos_your_daddy
#=> :father
...