Ruby Blocks, do or brace?

Posted by Rick DeNatale Tue, 02 Oct 2007 11:04:00 GMT
Conventional wisdom in Ruby is to use do/end to delimit blocks which contain more than one line of code, and braces for one-line blocks. I've always tended to loosely follow this advice.

Thanks to ruby-talk, I just became aware of Jim Weirich's suggestion to use braces for blocks when the value is being used, and do/end for blocks which are primarily sequences of statements. Jim actually posted this over three years ago, and Joe O'Brien brought it up more recently.

On the whole, I like this idea and will probably adopt it to tune my use of do/end vs. braces.


But don't all blocks return a value?

Here's Jim's prescription:

  • Use { } for blocks that return values
  • Use do / end for blocks that are executed for side effects

For the pedantic minded, it's true that all Ruby blocks actually return values. Every Ruby statement and expression, returns a value. Some folks have used this to object to the suggestion, or at least question it.

At the risk of putting words in Jim's mouth, I believe that he's really talking here about the use/non-use of the value of the block. If you use the value of the block then tilt more to using braces. If the value is discarded then lean towards do/end.

Why I Like this idea

Visually {x + 7} feels more like ( x + 7) than do; x + 7; end does.

Related Things

I've always been slightly uncomfortable writing code like this:

collection.select do | x |
  something
  something_else
end.some_other_method

Instead I think that:

collection.select { | x |
  something
  something_else
}.some_other_method

Just looks better to my eye, despite the multi-line block body.

When I first started writing this, I was thinking that this was an example of Jim's style, but it really isn't. First of all the value of the block is being used, but not directly in the code we're looking at. It's being used internally by the select method.

Now I don't think that you should use braces for any block passed to a method like select, even though the value is being used there.

Second, the value we're using here isn't the value of the block but the value of the select method call.

That said, I still like using the braces in this case.

Another tension

Keep in mind that you aren't completely free to substitute braces for do/end. Sometimes you need to bow to Ruby's precedence rules. Generally in Ruby if something has both a word and a symbolic representation, e.g. {} vs do/end, || vs or, etc. The symbolic version has higher syntactic precedence than the word version. This comes up at times, particularly when you are using a DSL in it's 'natural' mode and not using 'unnecessary' parentheses. For example in Rake:

description 'pickup up the leaves'
task :rake => pre_raking_tasks do
  #code to rake the leaves
end

Works as expected, but:

description 'pickup up the leaves'
task :rake => pre_raking_tasks {#code to rake the leaves}

Won't because it actually means

description 'pickup up the leaves'
task :rake => (pre_raking_tasks {#code to rake the leaves})

The block is given to the pre_raking_tasks invocation rather than the task invocation. So you either need to use do/end or parenthesize explicitly:

description 'pickup up the leaves'
task(:rake => pre_raking_tasks) {#code to rake the leaves}

Update 27 Apr 2009

Fixed a typo, an extra trailing parenthesis, pointed out by Brian Adkins.

Trackbacks

Use the following link to trackback from your own site:
http://talklikeaduck.denhaven2.com/trackbacks?article_id=465

Comments

  1. Jim Weirich about 5 hours later:

    The words you put in my mouth were the ones I would have put there myself :)

    On the “select{…}.method” thing, it is true that my rule concerns the use of the value returned by the block, not the value returned by select. But surprising, most of the time blocks that return values are used with methods that return values of interest. E.g. I’m almost never interested in the value returned by :each. But the value return by select is almost always useful (and a candidate for further method applications).

  2. James O'Kelly 5 days later:

    I agree with most of this, although returning a value isn’t what I use to determine braces or do end.

    It’s a multi-line thing for my, but as you say having an end.function() is really ugly, so that is when I break my multi-line rule.

  3. FrankLamontagne 10 days later:

    Hey, that’s a nice idea!

    I also think that :

    collection.select { | x |

    something
    something_else
    

    }.some_other_method

    looks better than the “do, end” version.