Splatting an argument does not obey left-to-right execution order
Ruby evaluates arguments left to right, but it does not appear to handle construction of the eventual argument list from left to right.
Take this example:
def foo(*args) p args end ary = [1,2] foo(*ary, ary.shift)
With left-to-right execution, the
ary value should be splatted (1, 2), and THEN shifted (1) producing
args == [1, 2, 1].
However, on MRI, the shift occurs before the splat, so
args == [2, 1].
This is counter-intuitive. At the moment in time the splat is encountered,
ary is still
[1, 2]. So the first two arguments should be (1, 2). THEN the shift happens, producing a third argument of (1).
This affects JRuby running Rails because they have a small piece of code that depends on this unusual behavior: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/callbacks.rb#L411-L425
This code appears to have been introduced into Rails recently, and I will file a separate issue to change it to be more explicit about ordering.
Updated by headius (Charles Nutter) over 3 years ago
It is worth mentioning that if the splatted value is not an array, we can see that
to_a does get called before
shift. That makes this even more unintuitive, since the calls happen in proper order but the assembly of the args list happens in the wrong order.
a = Object.new a.instance_variable_set :@ary, [1, 2] def a.to_a p :to_a @ary end def a.shift p :shift @ary.shift end def f(*x) p x end f(*a, a.shift) __END__ Output on MRI: :to_a :shift [2, 1] Output on JRuby: :to_a :shift [1, 2, 1]
All implementations I tested output
shift. However, as with the plain array, MRI delays the application of the splat until after the shift call.