I use this sometimes if I want to assign something to a variable but I have to calculate the value I want to assign first. It makes the code a little bit more tidy this way. I think it's user preference. Basically you are saying: I am assigning something to foo, but in order to get the value I want I first need to do some things. It's particularly useful when doing memoization, so instead of
if @cache.nil?
do_something!
@cache = read_value
end
You can do
@cache ||= begin
do_something!
read_value
end
What you are taking advantage here is that the Ruby interpreter has a stack, and each expression will usually push something on the stack, or take something from the stack. Assignment just takes the last thing from the stack and assigns it (in this case the last line from begin/end). Many times knowing this (stack approach in Ruby) can be useful.
I don't think it violates least surprise though, I think it's user preference wheather you want to use it or not.
You can see that it doesn't do anything unexpected by looking at what bytecode instructions it generates in Ruby MRI 1.9:
RubyVM::InstructionSequence::compile("c = begin; a = 5; 6; end").to_a
[:trace, 1],
[:trace, 1],
[:putobject, 5],
[:setlocal, 2],
[:trace, 1],
[:putobject, 6],
[:dup],
[:setlocal, 3],
[:leave]
Trace is just for stack traces, you can ignore that. Dup duplicates the last item on the stack. In this example the number of the local variable a
is 2
and the number of the local variable c
is 3
(hence putobject, 2
will assign to variable a
, etc).
The only side-effect of this compared to a = 5; c = 6
is the dup
instruction, which means the stack size of your method will be larger by 1 slot. But this is not particularly important because it only has any effect while the interpreter is inside this particular method, and memory for stack is pre-reserved anyway, so it only means the stack pointer will be decremented by 1 more than it would otherwise. So basically no change at all. With optimizations turned on even the dup
will probably disappear.