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

Типы переменных

При обсуждении области видимости переменных в Ruby важно сначала понять типы переменных, с которыми мы будем работать. Это связано с тем, что тип переменной будет влиять на правила области видимости, которым необходимо следовать. В Ruby существует 5 типов переменных: константы, глобальные переменные, переменные класса, переменные экземпляра и локальные переменные. Далее будет краткое описание каждого.

Следует отметить, что локальные переменные используются очень часто, поскольку они имеют узкую область действия. Использование локальных переменных может помочь избежать трудностей с именами в программе из-за их узкой области применения. Рубисты должны быть осторожны при именовании и использовании переменных с более широкой областью действия, таких как константы или глобальные переменные.

Константы

Константы не должны изменяться (хотя Ruby разрешает их изменять после выдачи предупреждения). Они доступны во всех рамках программы и имеют лексическую область действия.

CONSTANT = "Availability: e v e r y w h e r e"

Глобальные переменные

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

$global_var = "Availability: e v e r y w h e r e"

Переменные класса

Переменные класса доступны во всем классе и доступны для использования переменными экземпляра, а также самим классом. Однако они должны быть инициализированы на уровне самого класса. Затем их можно изменить с помощью методов класса или экземпляра. Переменные класса являются общими для всех экземпляров этого класса.

@@confusion_level = 0

Переменные экземпляра

Переменные экземпляра доступны во время экземпляра класса. Следовательно, каждый отдельный объект класса может иметь свои собственные персонализированные переменные экземпляра (т. е. 2 члена класса собак могут иметь разные переменные экземпляра, называемые @name, с присвоенными им 2 разными именами).

@instance_var = "Availability: During the current instance of the class"

Локальные переменные

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

variable = "Availability: Within the scope I was defined."

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

Создание области

В Ruby область действия переменной определяется определением метода или блоком.

При обсуждении области видимости в ruby ​​следует помнить, что внутренняя область может обращаться к переменным, инициализированным во внешней области, но внешняя область не может получить доступ к переменным, инициализированным во внутренней области.

Методы

Область видимости метода является автономной, то есть она не может получить доступ к локальным переменным, которые были инициализированы вне ее. Когда переменная инициализируется в теле метода, на нее можно ссылаться или изменять ее из тела метода. Переменные, инициализированные в автономной области, недоступны вне тела метода. Так как же методы могут получить доступ к переменным? Мы можем инициализировать их в теле метода или передать в метод в качестве параметров.

Давайте поработаем с примером, чтобы закрепить, как определение метода влияет на область действия локальной переменной:

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

plants_info = [["peace lily", "medium"], ["rubber plant", "medium"], ["bonsai", "high"], ["succulent", "low"]]
def plant_mom(plant_info)
  plant, watering_needs = plant_info[0], plant_info[1]
  case watering_needs
  when "low"
    watering_needs = "2"
  when "medium"
    watering_needs = "4"
  when "high"
    watering_needs = "6"
  end
  p plant_info # ==> ["succulent", "low"]
  p plants_info # raises an error! NameError: undefined local variable or method `plants_info' for main:Object
  "We should water the #{plant} #{watering_needs} times this week."
end
plant_mom(plants_info[3])

В приведенном выше примере мы инициализируем локальную переменную с именем plants_info в line 1 и присваиваем ее вложенному массиву. В lines 3-17 мы определяем метод plant_mom, который принимает 1 параметр. Определяя метод, мы создаем автономную область видимости, а это означает, что мы можем получить доступ только к тем переменным, которые инициализированы внутри тела метода, или переменным, которые передаются в метод. В line 19 мы вызываем метод plant_mom и передаем элемент нашей переменной plants_info в качестве параметра. В line 4 мы инициализируем 2 локальные переменные plant и watering_needs в теле метода и присваиваем их элементам из нашего массива plant_info. Мы можем получить доступ к этим элементам, так как они были переданы в метод при вызове line 19. Итак, line 14 мы можем распечатать массив plant_info из метода, потому что он был передан в метод при вызове line 19. Однако, когда мы пытаемся получить доступ к plants_info на line 14 из метода, мы не можем получить к нему доступ из-за автономной области действия метода. Локальная переменная была инициализирована вне метода, поэтому она недоступна внутри метода, когда мы пытаемся получить к ней доступ. Если бы мы попытались получить доступ к переменным plant или watering_needs за пределами тела метода, мы также не смогли бы получить к ним доступ из-за автономной области действия метода.

Перейдем к другому примеру:

plants_info = [["peace lily", "medium"], ["rubber plant", "medium"], ["bonsai", "high"], ["succulent", "low"]]
def plant_mom(plant_info)
  plant, watering_needs = plant_info[0], plant_info[1]
  case watering_needs
  when "low"
    watering_needs = "2"
  when "medium"
    watering_needs = "4"
  when "high"
    watering_needs = "6"
  end
  "We should water the #{plant} #{watering_needs} times this week."
end
puts plant_mom(plants_info[3]) # => We should water the succulent 2 times this week.
p plants_info # => [["peace lily", "medium"], ["rubber plant", "medium"], ["bonsai", "high"], ["succulent", "low"]]

В этом примере мы инициализируем локальную переменную plants_info в line 1 и присваиваем ее вложенному массиву. На lines 3-14 мы определяем метод с именем plant_mom, который принимает 1 параметр. Определяя метод, мы создаем автономную область видимости, а это означает, что мы можем получить доступ только к тем переменным, которые инициализированы внутри тела метода, или переменным, которые передаются в метод. В line 16 мы вызываем метод plant_mom и передаем элемент нашей переменной plants_info в качестве параметра. В line 4 мы инициализируем 2 локальные переменные plant и watering_needs в теле метода и присваиваем их элементам из нашего массива plant_info, который был передан в метод при вызове. В lines 5-12 мы используем оператор case, чтобы переназначить локальный vairbale watering_needs на основе того, что watering needs было назначено при инициализации. Оператор case не создает новую область, подобно тому, как оператор for не создает новую область, поскольку ни определения методов, ни блоки не являются. Определение метода всегда должно начинаться с def methodname..end. Способ определить, является ли do..end или {..} блоком и, следовательно, создает область, состоит в том, чтобы проверить, следуют ли эти термины за вызовом метода. Если оператор следует сразу за вызовом метода, он считается блоком и создается новая область. Продолжая пример, метод возвращает строку на line 13, используя локальные переменные, определенные на line 4, которые мы видим на выходе на line 14. На line 15 мы видим, что строка plants_info остается неизменной из-за инициализации локальных переменных на line 4.

plants_info = [["peace lily", "medium"], ["rubber plant", "medium"], ["bonsai", "high"], ["succulent", "low"]]
def plant_mom(plant_info)
  case plant_info[1]
  when "low"
    plant_info[1] = "2"
  when "medium"
    plant_info[1] = "4"
  when "high"
    plant_info[1] = "6"
  end
  "We should water the #{plant_info[0]} #{plant_info[1]} times this week."
end
puts plant_mom(plants_info[3]) # => We should water the succulent 2 times this week.
p plants_info # => [["peace lily", "medium"], ["rubber plant", "medium"], ["bonsai", "high"], ["succulent", "1"]]

У нас есть последний пример, который должен быть прямой противоположностью последнему. В этом примере мы инициализируем локальную переменную plants_info в line 1 и присваиваем ее вложенному массиву. На lines 3-13 мы определяем метод plant_mom, который принимает 1 параметр. Определяя метод, мы создаем автономную область видимости, а это означает, что мы можем получить доступ только к тем переменным, которые инициализированы внутри тела метода, или переменным, которые передаются в метод. В line 15 мы вызываем метод plant_mom и передаем элемент нашей переменной plants_info в качестве параметра. В lines 4-11 мы используем оператор case для переназначения элемента из массива plants_info, который передается в метод. В отличие от последнего примера, мы не переназначили элемент, который был передан из массива в относительно внешней области видимости, локальной переменной во внутренней области. Глядя на то, что выводится на line 16, мы видим, что, передав переменную из внешней области в метод, мы можем обновить локальную переменную во внешней области. После вызова метода plant_mom для line 15 массив plants info изменяется.

Блоки

Блоки обозначаются do..end или {} и создают новую область действия для локальных переменных. Область действия переменной определяется местом ее инициализации. Мы можем изменять переменные из внутренней области, и это изменение может повлиять на внешнюю область, поэтому важно правильно называть переменные. Как указано выше в одном из примеров метода, не все операторы do..end или {..} считаются блоками. Способ определить, является ли do..end или {..} блоком и, следовательно, создает область, состоит в том, чтобы проверить, следует ли он за вызовом метода. Если оператор следует сразу за вызовом метода, он считается блоком и создается новая область.

islands = ["The Big Island", "Kauai", "Maui", "Niihau"]
islands.each do |island| 
  adjective = "beautiful" 
  puts "#{island} is the most #{adjective} of all of the Hawaiian Islands."
end
puts adjective

В line 1 мы инициируем локальную переменную islands, которую присваиваем массиву строк. В line 3 мы вызываем метод each для локальной переменной islands. Блок do..end следует за вызовом метода each и, следовательно, создает новую область с island в качестве параметра блока. Мы инициализируем локальную переменную adjective на line 4 во внутренней области блока. На line 5 мы используем как параметр нашего блока island, так и нашу локальную переменную adjective для вывода оператора. Однако на line 8, когда мы пытаемся вывести нашу локальную переменную adjective, ruby ​​больше не имеет доступа к переменной adjective и поэтому вызывает NameError. Это связано с тем, что блоки создают внутреннюю область, и это классический случай, когда внешняя область не может получить доступ к переменным, инициализированным во внутренней области. Посмотрим, сможем ли мы это исправить...

islands = ["The Big Island", "Kauai", "Maui", "Niihau"]
adjective = "green"
islands.each do |island| 
  adjective = "beautiful" 
  puts "#{island} is the most #{adjective} of all of the Hawaiian Islands."
end
puts adjective

Этот код должен работать без ошибок. Это связано с тем, что мы добавили инициализацию локальной переменной в line 2, которая находится в относительно внешней области по сравнению с внутренней областью, которую блок создает в lines 4-7 с помощью do..end сразу после вызова метода each. Поэтому line 9, который также находится в относительно внешней области, теперь имеет доступ к локальной переменной adjective. Но что выведет line 9? Он выводит beautiful, потому что мы смогли переназначить локальную переменную adjective из line 2. Мы сделали это без передачи переменной в блок. Это важное отличие от определения метода.

island = "Kauai"
count = 0 
while count < 4 do 
  adjective = "beautiful" 
  puts "#{island} is the most #{adjective} of all of the Hawaiian Islands."
  count += 1
end
puts adjective

У нас есть еще один пример, который немного отличается от двух предыдущих, но важно добавить его для сравнения. В этом примере мы инициализируем 2 локальные переменные на lines 1 and 2. В line 4 мы используем while, который является частью языка ruby, за которым следует оператор do..end. На line 5 мы и инициализируем 3-ю локальную переменную. Однако в этом примере нет вызова метода, поскольку while не является методом, поэтому оператор do..end не обозначает блок и, следовательно, не создает внутреннюю область. Это продемонстрировано на line 10, где мы все еще можем получить доступ к локальной переменной adjective, и код работает без ошибок. Важно помнить, что do..end и {..} не определяют автоматически блок и не инициализируют внутреннюю область.

В этой статье мы обсудили переменные, область действия переменных с помощью локальных переменных и то, как область действия переменных связана с определениями и вызовами методов. Более подробное обсуждение области видимости и ее отношения к привязкам см. в моей следующей статье Привязка в Ruby.