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

У меня есть массив объектов:

class Person
  attr_accessor :email

  def initialize(email)
    @email = email
  end
end

array = [
  Person.new('hello@gmail.com'),
  Person.new('world@gmail.com')
]

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

clone = array.clone
clone.map { |obj|
  obj.email.upcase!
  obj
}

puts array.inspect # why is the original being mutated
puts clone.inspect

Изменяет исходный массив. Я экспериментировал как с dup, так и с clone. и я получаю тот же результат. Почему map мутирует объекты при работе с атрибутом каждого элемента?

1 Ответ

3 голосов
/ 25 марта 2019

Вы клонировали массив , содержащий Person ссылки, но вы не изменили массив; Вы изменили сами Person экземпляры. clone - это так называемый «неглубокий клон», который копирует только объект-получатель, но ни один из объектов, чьи ссылки он может содержать.

В реальной логике: вы взяли лист бумаги, на котором написали «Дженни, Тимми». Затем вы скопировали его на другой лист бумаги. Затем вы взяли первый лист бумаги, нашли людей, на которых он ссылался, и дали им яблоко. Затем вы взяли второй лист бумаги, нашли на нем людей и задались вопросом, откуда взялись их яблоки. Но есть только один Тимми, только одна Дженни: вы даете в первом списке Дженни яблоко, во втором списке Дженни тоже есть.

Если вы хотите что-то клонировать, клонируйте Дженни .

array.map { |person|
  person.clone.yield_self { |clone|
    clone.email = clone.email.upcase
  }
}

(Обратите внимание, что я не использовал clone.email.upcase!. Причина - одна и та же причина снова и снова: если вы клонируете объект, они оба будут использовать одну и ту же строку для email. upcase! изменения этой строки, который будет в верхнем регистре электронной почты клона и электронной почты оригинала. Таким образом, мы создаем новую строку электронной почты для клона.)

Подобные вещи лучше всего понять, перейдя к визуализации с помощью этого инструмента . Тем не менее, инструмент запускает Ruby 2.2, который не знает о yield_self; этот код эквивалентен:

array.map { |person|
  clone = person.clone
  clone.email = clone.email.upcase
  clone
}

Вы могли бы также написать это, хотя это не будет отображаться так четко:

array.map(&:clone).map { |clone|
  clone.email = clone.email.upcase
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...