Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby 'tap' method - inside assignment

Tags:

ruby

Recently I discovered that tap can be used in order to "drily" assign values to new variables; for example, for creating and filling an array, like this:

array = [].tap { |ary| ary << 5 if something }

This code will push 5 into array if something is truthy; otherwise, array will remain empty.

But I don't understand why after executing this code:

array = [].tap { |ary| ary += [5] if something }

array remains empty. Can anyone help me?

like image 986
mdesantis Avatar asked Jan 27 '11 16:01

mdesantis


2 Answers

In the first case array and ary point to the same object. You then mutate that object using the << method. The object that both array and ary point to is now changed.

In the second case array and ary again both point to the same array. You now reassign the ary variable, so that ary now points to a new array. Reassigning ary however has no effect on array. In ruby reassigning a variable never effects other variables, even if they pointed to the same object before the reassignment.

In other words array is still empty for the same reason that x won't be 42 in the following example:

x = 23
y = x
y = 42 # Changes y, but not x

Edit: To append one array to another in-place you can use the concat method, which should also be faster than using +=.

like image 151
sepp2k Avatar answered Dec 18 '22 19:12

sepp2k


I want to expand on this a bit:

array = [].tap { |ary| ary << 5 if something }

What this does (assuming something is true-ish):

  1. assigns array to [], an empty array.

    array.object_id = 2152428060
    
  2. passes [] to the block as ary. ary and array are pointing to the same array object.

    array.object_id = 2152428060
    ary.object_id = 2152428060
    
  3. ary << 5 << is a mutative method, meaning it will modify the receiving object. It is similar to the idiom of appending ! to a method call, meaning "modify this in place!", like in .map vs .map! (though the bang does not hold any intrinsic meaning on its own in a method name). ary has 5 inserted, so ary = array = [5]

    array.object_id = 2152428060
    ary.object_id = 2152428060
    

We end with array being equal to [5]

In the second example:

array = [].tap{ |ary| ary += [5] if something }    
  1. same
  2. same
  3. ary += 5 += is short for ary = ary + 5, so it is first modification (+) and then assignment (=), in that order. It gives the appearance of modifying an object in place, but it actually does not. It creates an entirely new object.

    array.object_id = 2152428060
    ary.object_id = 2152322420
    

So we end with array as the original object, an empty array with object_id=2152428060 , and ary, an array with one item containing 5 with object_id = 2152322420. Nothing happens to ary after this. It is uninvolved with the original assignment of array, that has already happened. Tap executes the block after array has been assigned.

like image 26
bambery Avatar answered Dec 18 '22 19:12

bambery