Reputation: 1
I have an array of objects:
class Person
attr_accessor :email
def initialize(email)
@email = email
end
end
array = [
Person.new('[email protected]'),
Person.new('[email protected]')
]
I created a clone from the original array to perform map function, and then I mapped over each element to make its email attribute become uppercase:
clone = array.clone
clone.map { |obj|
obj.email.upcase!
obj
}
puts array.inspect # why is the original being mutated
puts clone.inspect
It mutates the original array. I have experimented with both dup
and clone
. and I get the same result. Why does map
mutate the objects when operating on each element's attribute?
Upvotes: 0
Views: 312
Reputation: 198436
You cloned the array containing Person
references, but you did not change the array; you changed the Person
instances themselves. clone
is so-called "shallow clone", which copies only the receiver object, but none of the objects whose references it may contain.
In real-world logic: you took a piece of paper on which you wrote "Jenny, Timmy". Then you copied it to another piece of paper. You then took the first piece of paper, found the people it refered to, and gave them an apple. Then you took the second piece of paper, found the people on it, and wondered where their apples came from. But there's only one Timmy, only one Jenny: you give the first list's Jenny an apple, the second list's Jenny also has one.
If you want to clone something, clone Jenny.
array.map { |person|
person.clone.yield_self { |clone|
clone.email = clone.email.upcase
}
}
(Note that I didn't use clone.email.upcase!
. The reason is the same reason all over again: if you clone an object, they will both use the same string for email
. upcase!
changes that string, which would uppercase both clone's email and the original's email. Thus, we make a new email string for the clone.)
Things like this are best understood by stepping through the visualisation using this tool. However, the tool runs Ruby 2.2, which doesn't know about yield_self
; this code is equivalent:
array.map { |person|
clone = person.clone
clone.email = clone.email.upcase
clone
}
You could also write this, though it won't visualise as clearly:
array.map(&:clone).map { |clone|
clone.email = clone.email.upcase
}
Upvotes: 3