New Relic Now Start training on Intelligent Observability February 25th.
Save your seat.
현재 이 페이지는 영어로만 제공됩니다.

This is part 1 of a series on Weird Ruby. Don’t miss Weird Ruby Part 2: Exceptional EnsuranceWeird Ruby Part 3: Fun with the Flip-Flop Phenom, and Weird Ruby Part 4: Code Pods (Blocks, Procs, and Lambdas).

This is the first in a series of posts inspired by my recent speaking engagement at the Keep Ruby Weird conference in Austin, Texas. I’ve been using Ruby full-time for several years and have seen plenty of things along the way that made me scratch my head. As I’ve gotten to know a bit more about the language, the reasons for some of those design decisions have become clearer to me, and I’d like to share some of that perspective.

keep ruby weirdThis series is about some of the unusual and potentially counterintuitive behaviors of Ruby, particularly those that surprise new developers or newcomers from other languages. It’s not intended to be yet another list of ‘WTF Ruby?!’ code samples. Rather, I hope to present an alternative view of some of these behaviors and why they are in fact important to how we use the language.

Enter the begin-end block, a seemingly innocuous control structure that usually doesn’t affect much at all in your code. Combined with rescue it can collect and handle errors, and if we add an else to the rescue we can run code when we don’t see errors. Begin-end blocks can also be used with an ‘ensure’ block to guarantee that some code will run. It’s when you combine them with conditionals that… unexpected things start to happen. Let’s start at the beginning.

Reticulating the splines

The begin in a lone begin-end block doesn’t really begin anything, nor does the end actually end anything. Wrapping code in a begin-end block alone doesn’t change the behavior of your code at all, which itself is quite weird.

Have a look at this example:



@splines.reticulate

We have some splines that need reticulating, as they often do, and we’re going to wrap them in a begin-end block and see what changes:



begin

  @splines.reticulate

end

All of the behavior for the code in these examples is the same. The return value is the same and the bytecode generated is almost exactly the same.

How do we know that? You can use a RubyVM::InstructionSequence to get a peek at the bytecode for your Ruby. This is the bytecode for our reticulating splines example:

Without begin-end:



0000 trace            1                                               (   1)

0002 getinstancevariable :@splines, 

0005 opt_send_simple  <callinfo!mid:reticulate, argc:0, ARGS_SKIP>

0007 leave

With begin-end:



0000 trace            1                                               (   1)

0002 trace            1

0004 getinstancevariable :@splines, 

0007 opt_send_simple  <callinfo!mid:reticulate, argc:0, ARGS_SKIP>

0009 leave

Ruby uses a stack-based virtual machine (VM), so values are pushed onto the stack and then pulled off one at a time to act on the next value, until we reach the beginning state and return the result.

The first instruction is a trace, which we’ll get back to in just a moment. The second instruction is getinstancevariable :@splines, which will do about what you expect: it will get the value of @splines. The next opt_send_simple line sends the reticulate message to the @splines value, and the final leave instruction tells us we’re all done.

That trace instruction we skipped over earlier enables Ruby’s TracePoint functionality. You can use TracePoint to run some code for each statement in your program. Notice that the second begin-end example actually has an extra trace instruction; that trace is for the begin-end block. The TracePoint feature is a great deal of fun, go check out the documentation and play around. Just remember not to do that sort of thing in production, it will slow down your application considerably.

Will the splines reticulate?

I think we’ve pretty well established that begin-end blocks by themselves don’t do much, so let’s see what happens when we use them with some conditionals. Time for the ‘Will it reticulate?’ game:



@splines.reticulate if false

Will it reticulate? Absolutely not, those splines are dead in the water and our ship is likely to crash because of it. That if conditional functions exactly as you’d think it would.

How about this?



begin

  @splines.reticulate

end if false

No? You’re correct again. If we don’t get reticulating soon we’re doomed. Lets see if this will reticulate:



@splines.reticulate while false

Unfortunately not. Well, if that didn’t work, surely this won’t work either:



begin

  @splines.reticulate

end while false

Surprise! This will actually reticulate our splines. Just once, but everyone knows once is enough for a good set of splines.

Here we’ve stumbled across Ruby’s do-while construct, the begin-end-while loop. This is a post-condition loop: it checks the loop condition only after processing the body of the loop. The code above will reticulate the splines once, then check the boolean post-condition and determine that it should no longer reticulate.

More often than not, this begin-end-while loop surprises the hapless developer who stumbles upon it, as begin-end blocks in and of themselves are not generally expected to change behavior (as we saw before). Matz himself has said that he regrets this behavior in Ruby, and that he would much prefer developers use something like this instead:



loop do

  @splines.reticulate

  break if true

end

The effect of the suggested loop-break construct is exactly the same: we’ll always reticulate the splines exactly once. The only difference is that the loop code is much less likely to confuse.

Weird Incoming

Tune in soon for more weird Ruby and we’ll look at some other potentially unexpected behavior: the possibility that your ensure block will not actually ensure anything.

 

Ruby image courtesy of Shutterstock.com.  For more on Ruby, see our Ruby developers spotlight. Curious on other New Relic news and integrations? See our Infrastructure monitoring platform