The Procedure/Function Block Convention in Ruby

Ruby lets you enclose blocks in either {...} or do...end delimiters. Which you choose is a matter of style.

There are two conventions that I know of for deciding which form to use.  The one I see people using most often these days is the “line count” convention: curly brackets for one-liners, do...end for multiline statements. E.g.:

objects.each {|o| o.frobnicate!}

objects.each do |o|
  o.fold!
  o.spindle!
  o.mutilate!
end

The alternative—and the convention I’ve been using for a while—is based on the semantic intent of the block, rather than on its length. It can be stated like this:

  • Use curly brackets for functional blocks, where the primary purpose of the block is to return a value.
  • Use do...end for procedural blocks, where the primary purpose of the block is its side-effects.  That is, the block is intended to change the state of the system in some way or to perform output.

Here are some examples of functional blocks:

arr2 = arr.map{|x| x.to_s.trim }

# Read from the world, but don't change the world
options = open('options.yml') { |f|
  YAML.load(f)
}

# Generate some XML and assign it to a variable
xml = Nokogiri::XML::Builder.new { |xml|
  xml.root {
    xml.products {
      xml.widget {
        xml.id_ "10"
        xml.name "Awesome widget"
      }
    }
  }.to_xml

# Hash fetch with default value
err_count = stats.fetch(:error_count) { 0 }

And here are some examples of procedural blocks:

# Change an array in place
arr.map! do |x| x.to_s.trim end

# Write output to a file
open("$0.pid", 'w+') do |f|
  f.write($$.to_s)
end

# This generates a global routes map as a side-effect
Rails.application.routes.draw do
  resources :users
  # ...
end

# Generates a test_* method and adds it to the suite
test "should return the square of its input" do
  assert_equal(4, foo(2))
end

# Hash fetch with required key
out = options.fetch(:output) do 
  raise ArgumentError, "An output option must be specified"
end

I can’t take credit for this convention. I learned it from someone else (I want to say Nick Evans?). I’m sure there have been other blog posts about it, but I haven’t seen anyone talking about it recently.

I’m not going to tell you this is the way you should write your code. Nor am I perfectly consistent; there are a few cases where I use curly brackets for statements that are technically procedural, simply because I think it reads a lot better. Notably, for certain RSpec constructs:

describe "a user with a name" do
  subject{User.new(:name = "Bob")} 
  it { should be_valid }
end  

But by and large I’ve been pretty happy with this convention, and I think it has some advantages.

  • It adds a visual cue about the intent of the code that wouldn’t otherwise be there.
  • I never liked switching back and forth between curlies and do...end just because I added or removed a little bit of code. Granted, editor macros can make this easier, but it just felt like arbitrary extra work.
  • The use of do...end feels like a procedural, imperative statement to me. Functional blocks that use do...end don’t read quite as well in my eyes.
  • EDIT As Nick Morgan points out below, chaining method calls onto an end looks kinda weird (do ... end.foo). Since it’s rare to want to chain method calls onto a procedural block, that’s not an issue for code that uses this convention.

So there ya go, I just thought I’d toss that out for anyone who hadn’t been exposed to it. Comments pro or con are welcome… or if you have another convention for block syntax that I didn’t list above, I’d love to hear about it.

EDIT: As Roberto Decurnex correctly points out below, curly bracket blocks also have a different parser precedence. Regardless of which block convention you adopt, always remember to put your method parameters inside parens if calling the method with a curly block.

EDIT: It occurred to me that there’s actually a third convention I know of: “pick one form and stick to it”. But I haven’t seen that one in the wild lately.

EDIT THE THIRD: Revisiting this post, I’ll add that everyone I know who uses this convention, including James Gray, traces their use back to Jim Weirich, so I’ve taken to calling it the “Weirich convention”. Here’s Jim posting about it all the way back in 2004.