I was writing up some specs for evaluation order today and discovered
something rather interesting:
before :each do
@obj = Object.new
def @obj.foo(a, b, &c)
[a, b, c ? c.call : nil]
end
end
it "evaluates block pass before arguments" do
a = 0
p = proc {true}
@obj.foo(a += 1, a += 1, &(a += 1; p)).should == [2, 3, true]
a.should == 3
end
it "evaluates block pass before receiver" do
p1 = proc {true}
p2 = proc {false}
p1.should_not == p2
p = p1
(p = p2; @obj).foo(1, 1, &p).should == [1, 1, true]
end
This was unexpected for me. Argument order being left-to-right seems
reasonable, and I think it's what most people would expect looking at
the code. But having the block pass argument evaluated not only before
the other args but before the receiver seems broken.
Unlike Ruby 1.8, Ruby 1.9 and JRuby behave the same, with the order
being exactly what you'd expect:
CODE
obj = Object.new
def obj.foo(a, b, &c)
[a, b, c ? c.call : nil]
end
pr = proc {3}
p ((p 'a'; obj).foo((p 'b'; 1), (p 'c'; 2), &(p 'd'; pr)))"
Ruby 1.8:
"d"
"a"
"b"
"c"
[1, 2, 3]
Ruby 1.9:
"a"
"b"
"c"
"d"
[1, 2, 3]
JRuby:
"a"
"b"
"c"
"d"
[1, 2, 3]
Rubinius still evaluates arguments in reverse order, and then evaluates
block pass after the arguments but before the receiver. I believe this
is being "fixed", but at the moment it's the most unexpected behavior of
the four:
"c"
"b"
"d"
"a"
[1, 2, 3]
- Charlie