В этой статье я расскажу о том, как идентифицировать и понимать затенение переменных и аргументы мутирующих методов. Я покажу примеры затенения переменных и мутирующих аргументов метода, однако, чтобы по-настоящему понять эти концепции, вы должны иметь некоторые предварительные знания об области видимости переменных, переназначении и определениях методов, чтобы полностью понять их в различных ситуациях. Я сделаю все возможное, чтобы кратко рассказать о других концепциях, но эта статья в основном посвящена затенению переменных и аргументам методов мутации.
Во-первых, я начну с 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 и, в качестве бонуса, лучше понять, на что указывают объекты.