Oleg L
Oleg L

Reputation: 11

Unexpected effects of map and map! in Ruby

How is that possible that map changes the original array where it shouldn't?

arr = ['Foo','Bar','Tango']
arr.map do |name|
  name[0] = 'A'
end
arr # => ["Aoo", "Aar", "Aango"]

And why does the same block in map! make` a totally different return?

arr = ['Foo','Bar','Tango']
arr.map! do |name|
  name[0] = 'A'
end
arr # => ["A", "A", "A"]

Upvotes: 0

Views: 164

Answers (2)

Jörg W Mittag
Jörg W Mittag

Reputation: 369428

How is that possible that map changes the original array where it shouldn't?

arr = ['Foo','Bar','Tango']
arr.map do |name|
  name[0] = 'A'
end
arr # => ["Aoo", "Aar", "Aango"]

It doesn't. map doesn't change the array, your block does. Actually, to be really precise: the block doesn't change the array either, it changes each element of the array.

And why does the same block in map! make` a totally different return?

arr = ['Foo','Bar','Tango']
arr.map! do |name|
  name[0] = 'A'
end
arr # => ["A", "A", "A"]

It doesn't. The return values of map and map! in your examples are the same. (Well, not really: map returns a new array, map! returns the same array, modified, but the contents of those two returned arrays are he same.) But you never even look at the return value, you simply ignore it, so how can you make the statement that the return values are different?

map! mutates the array by replacing every element with the value of the block. The block contains an assignment expression, assignment expressions evaluate to their left-hand-side, ergo the block returns 'A' for every element, which means that every element is replaced with 'A'.

Note that map does the same thing:

arr = %w[Foo Bar Tango]
new_arr = arr.map do |name| name[0] = 'A' end
arr     #=> ["Aoo", "Aar", "Aango"]
new_arr #=> ["A", "A", "A"]

Upvotes: 2

metaphori
metaphori

Reputation: 2811

In the first example, the map produces a new array (which is immediately discarded, since it is not assigned to a var), however the block contains the side-effect of modifying the objects contained in the array (i.e., the array itself is not touched, but nothing prevents you to get an object from the array and change it---if it is mutable; and btw, strings are mutable, in fact s = "hi"; s[0]="x"; s # => "xi").

In the second example, the map! does change the original array, with the result value of the block, which is 'A' for each element (it is the result of the assignment expression, which incidentally also modifies the original elements of the array--but they are discarded since replaced accordingly to the semantics of map!).

From the ruby-doc:

  • map { |item| block } → new_ary: Invokes the given block once for each element of self. Creates a new array containing the values returned by the block.
  • map! {|item| block } → ary: Invokes the given block once for each element of self, replacing the element with the value returned by the block.

Upvotes: 4

Related Questions