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

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

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

#1  a = 5
#2  b = 3
#4  4.times do |a|
#5    a = 8
#6    puts a
#7  end
#9  puts a
#10 puts b

Что тут происходит? Мы думаем, что переназначаем a в строке 5, но когда мы выводим код в строке 9, терминал отобразит целое число 5, а не целое число 8. Что на самом деле происходит, так это то, что Ruby ищет переменную a и находит ее в блоке, как показано в параметре в строке 3. Ruby находит a, поэтому затем игнорирует переменную внешней области видимости, потому что никогда не увидит a, инициализированную в строке. 1. Когда происходит затенение переменных, это не позволяет нам вносить изменения во внешнюю область видимости a .

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

Чтобы исправить эту проблему с кодированием, мы должны изменить имя параметра, чтобы оно содержало уникальное имя, иначе произойдет затенение переменной. Если бы мы изменили a на c, это решило бы нашу проблему затенения переменных. Код в строке 9 теперь будет выводить целое число 8.

Если ваши знания об области видимости переменных и переназначении все еще размыты, то некоторые вещи все еще могут сбивать с толку, потому что вы можете спросить себя: «Доступен ли a в блоке do..end?» «Возможно ли переназначение внутри блока do..end?» «Если ответ да, то всегда ли он верен?». Чтобы ответить на некоторые вопросы, которые могут возникнуть у вас в голове, давайте рассмотрим еще один пример.

#1  integer = 13
#3  def some_method(parameter)
#4    integer = 10
#5    a = 5
#6    b = 3
#8    4.times do |integer|
#9      integer = 6
#10     puts integer
#11   end
#13   puts a
#14   puts b
#15   puts integer
#16 end
#18 some_method(integer)
#19 puts integer

Есть несколько вещей, которые мы должны отслеживать при просмотре этого кода, но самое важное, на что следует обратить внимание, это переменная integer. Когда затенение переменной происходит в строке 8 и 9, мы не получаем доступ к integer из строки 1 или строки 4, потому что Ruby только что получил доступ к integer из строки 8. Даже если мы исправим затенение переменной, переименовавinteger в строке 8, мы все равно не получим доступ к integer из строки 1. Мы обращаемся к целому числу из строки 4, потому что оно доступно для доступа за пределами блока do..end, но мы не можем получить доступ к integer из строки 1, хотя это первая строка кода. Ruby ищет integer, когда мы переназначаем его в строке 9 (если затенение переменной было исправлено), но мы по-прежнему соблюдаем правила определения метода. Еще одна вещь, на которую следует обратить внимание, это то, что после того, как мы исправили затенение переменных, мы затем переназначили integer в определении метода, так что это не меняет integer в строке 19. integer в строке 19 останется указывать на тот же объект, что и в строке 1.

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

Изменение аргументов метода

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

#1 def method_example(string)
#2   string << 'string argument'
#3 end
#5 a = 'string object'
#6 method_example(a)
#7 puts a

Как мы видим здесь, в строке 2, мы вызываем метод << для объекта, на который указывает string, и передаем строковый объект string argument в качестве аргумента. Важное отличие, на которое следует обратить внимание, заключается в том, что я сказал, что мы вызываем метод << для объекта, на который string УКАЗЫВАЕТ. Мы вызываем << не для string, а для объекта, на который он указывает, потому что мы не можем изменить переменную. Переменная используется для хранения информации, и мы хотим изменить объект, на который ссылается переменная. Другой важный аспект этой проблемы заключается в том, что в строке 6 мы вызываем method_example и передаем a в качестве аргумента. string теперь указывает на тот же строковый объект, на который указывает a. Почему это важно, спросите вы? Это связано с тем, что string ограничен на уровне определения метода, а объект, на который указывают string и a, мутирует DID. Поскольку, как я упоминал ранее, мы вызываем метод << для объекта, на который указывает string, это означало, что исходный объект был мутирован, даже если это было сделано на уровне определения метода. string .

Теперь, как именно это произошло? Это из-за аргументов и параметров. Аргументы — это фрагменты информации, которые мы отправляем вызову метода для модификации для получения определенного результата. Параметр — это когда определению метода необходимо получить доступ к информации за пределами его области, чтобы использовать ее в определении метода. Мы передали a в качестве аргумента методу method_example и присвоили этот аргумент переменной string (параметру), чтобы мы могли использовать объект, на который он указывает, как нам хочется. Таким образом, в строке 6, когда мы вызываем method_example, мы передаем a в качестве аргумента вместо параметра string. Затем код в определении метода выполняется с локальной переменной string, оцениваемой как объект, на который указывает a. Вот почему, когда мы вызываем метод puts и передаем a в качестве аргумента в строке 7, строковый объект, на который он указывает, теперь имеет значение string objectstring argument, поскольку исходный объект был видоизменен.

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

#1  def another_method(string)
#2    string += string.downcase! + 'FGH'
#3    string.concat( 'XYZ' )
#4    puts string.object_id # <= 15531780
#5  end
#7  a = 'ABC'
#8  b = another_method(a)
#10 puts a.object_id      # <= 15531840
#11 puts b.object_id      # <= 8

Здесь, в строке 2, мы вызываем деструктивный метод downcase! для объекта, на который указывает string, который изменит исходный объект. Однако, несмотря на то, что мы делаем это в контексте переназначения с помощью оператора +=, исходный объект все еще видоизменяется, потому что мы вызываем метод downcase! для объекта, на который указывает string.

Еще одно отличие заключается в том, что мы вызываем идентификаторы объектов, и код показывает, что мы указываем на несколько разных объектов. в строке 2 мы привязываем string к совершенно новому объекту, поэтому, несмотря на то, что мы изменили вызывающую программу в строке 2, мы не изменили вызывающую программу с дополнительным строковым объектом FGH . Это связано с тем, что переназначение не является мутативным, конкатенация строк не мутативна, и мы привязываем string к совершенно новому объекту. Мы можем доказать это, увидев идентификаторы объектов переменных и увидев, что все они разные. Также имейте в виду, что номера идентификаторов объектов, скорее всего, будут отличаться от ваших.

Это должно прояснить понимание Variable Shadowing, Muting Method Arguments и, в качестве бонуса, лучше понять, на что указывают объекты.