do..end vs curly braces for blocks in Ruby

Ruby on-RailsRubyRuby on-Rails-3Coding Style

Ruby on-Rails Problem Overview


I have a coworker who is actively trying to convince me that I should not use do..end and instead use curly braces for defining multiline blocks in Ruby.

I'm firmly in the camp of only using curly braces for short one-liners and do..end for everything else. But I thought I would reach out to the greater community to get some resolution.

So which is it, and why? (Example of some shoulda code)

context do
  setup { do_some_setup() }
  should "do somthing" do
    # some more code...
  end
end

or

context {
  setup { do_some_setup() }
  should("do somthing") {
    # some more code...
  }
}

Personally, just looking at the above answers the question for me, but I wanted to open this up to the greater community.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

The general convention is to use do..end for multi-line blocks and curly braces for single line blocks, but there is also a difference between the two that can be illustrated with this example:

puts [1,2,3].map{ |k| k+1 }
2
3
4
=> nil
puts [1,2,3].map do |k| k+1; end
#<Enumerator:0x0000010a06d140>
=> nil

This means that {} has a higher precedence than do..end, so keep that in mind when deciding what you want to use.

One more example to keep in mind while you develop your preferences.

The following code:

task :rake => pre_rake_task do
  something
end

really means:

task(:rake => pre_rake_task){ something }

And this code:

task :rake => pre_rake_task {
  something
}

really means:

task :rake => (pre_rake_task { something })

So to get the actual definition that you want, with curly braces, you must do:

task(:rake => pre_rake_task) {
  something
}

Maybe using braces for parameters is something you want to do anyways, but if you don't it's probably best to use do..end in these cases to avoid this confusion.

Solution 2 - Ruby on-Rails

From Programming Ruby: > Braces have a high precedence; do has a low precedence. If the method invocation has parameters that are not enclosed in parentheses, the brace form of a block will bind to the last parameter, not to the overall invocation. The do form will bind to the invocation.

So the code

f param {do_something()}

Binds the block to the param variable while the code

f param do do_something() end

Binds the block to the function f.

However this is a non-issue if you enclose function arguments in parenthesis.

Solution 3 - Ruby on-Rails

There a few points of view on this, it's really a matter of personal preference. Many rubyists take the approach you do. However, two other styles that are common is to always use one or the other, or to use {} for blocks that return values, and do ... end for blocks that are executed for side effects.

Solution 4 - Ruby on-Rails

There is one major benefit to curly braces - many editors have a MUCH easier time of matching them, making certain types of debugging much easier. Meanwhile, the keyword "do...end" is quite a bit harder to match, especially since "end"s also match "if"s.

Solution 5 - Ruby on-Rails

The most common rule I've seen (most recently in Eloquent Ruby) is:

  • If it's a multi-line block, use do/end
  • If it's a single line block, use {}

Solution 6 - Ruby on-Rails

I'm voting for do / end

The convention is do .. end for multiline and { ... } for one-liners.

But I like do .. end better, so when I have a one liner, I use do .. end anyway but format it as usual for do/end in three lines. This makes everyone happy.

  10.times do 
    puts ...
  end

One problem with { } is that it is poetry-mode-hostile (because they bind tightly to the last parameter and not the entire method call, so you must include method parens) and they just, to my mind, don't look as nice. They are not statement-groups and they clash with hash constants for readability.

Plus, I've seen enough of { } in C programs. Ruby's way, as usual, is better. There is exactly one type of if block, and you never have to go back and convert a statement into a compound-statement.

Solution 7 - Ruby on-Rails

A couple influential rubyists suggest to use braces when you use the return value, and do/end when you don't.

http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (on archive.org)

http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc (on archive.org)

This seems like a good practice in general.

I'd modify this principle a bit to say that you should avoid using do/end on a single line because it's harder to read.

You do have to be more careful using braces because it'll bind to a method's final param instead of the whole method call. Just add parentheses to avoid that.

Solution 8 - Ruby on-Rails

Actually it's a personal preference, but having said that, for past 3 years of my ruby experiences what I have learnt is that ruby has its style.

One example would be, if you are comming from a JAVA background , for a boolean method you might use

def isExpired
  #some code
end 

notice the camel case and most often 'is' prefix to identify it as a boolean method.

But in ruby world, the same method would be

def expired?
  #code
end

so I personally think, it's better to go with 'ruby way' (But I know it takes some time for one to understand (it took me around 1 year :D)).

Finally, I would go with

do 
  #code
end

blocks.

Solution 9 - Ruby on-Rails

I put another Answer, although the big difference was already pointed out (prcedence/binding), and that can cause hard to find problems (the Tin Man, and others pointed that out). I think my example shows the problem with a not so usual code snippet, even experienced programmeres do not read like the sunday times:

module I18n
	extend Module.new {
		old_translate=I18n.method(:translate)
		define_method(:translate) do |*args|
			InplaceTrans.translate(old_translate, *args)
		end
		alias :t :translate
	}
end

module InplaceTrans
	extend Module.new {
		def translate(old_translate, *args)
			Translator.new.translate(old_translate, *args)
		end
	}
end

Then i did some code beautifying ...

#this code is wrong!
#just made it 'better looking'
module I18n
	extend Module.new do
		old_translate=I18n.method(:translate)
		define_method(:translate) do |*args|
			InplaceTrans.translate(old_translate, *args)
		end
		alias :t :translate
	end
end

if you change the {} here to do/end you will get the error, that method translate does not exist ...

Why this happens is pointed out here more than one - precedence. But where to put braces here? (@the Tin Man: I always use braces, like you, but here ... overseen)

so every answer like

> If it's a multi-line block, use do/end > If it's a single line block, use {}

is just wrong if used without the "BUT Keep an eye on braces / precedence!"

again:

extend Module.new {} evolves to extend(Module.new {})

and

extend Module.new do/end evolves to extend(Module.new) do/end

(what ever the result of extend does with the block ...)

So if you want to use do/end use this:

#this code is ok!
#just made it 'better looking'?
module I18n
	extend(Module.new do 
		old_translate=I18n.method(:translate)
		define_method(:translate) do |*args|
			InplaceTrans.translate(old_translate, *args)
		end
		alias :t :translate
	end)
end

Solution 10 - Ruby on-Rails

Comes down to personal bias, I prefer curly braces over a do/end block as its more understandable to a higher number of developers due to a majority of background languages use them over the do/end convention. With that being said the real key is to come to an agreement within your shop, if do/end is used by 6/10 developers than EVERYONE should be using them, if 6/10 use curly braces, then stick to that paradigm.

Its all about making a pattern so that the team as a whole can identify the code structures quicker.

Solution 11 - Ruby on-Rails

There is a subtle difference between them, but { } binds tighter than do/end.

Solution 12 - Ruby on-Rails

My personal style is to emphasize readability over rigid rules of {...} vs do...end choice, when such choice is possible. My idea of readability is as follows:

[ 1, 2, 3 ].map { |e| e + 1 }      # preferred
[ 1, 2, 3 ].map do |e| e + 1 end   # acceptable

[ 1, 2, 3 ].each_with_object [] do |e, o| o << e + 1 end # preferred, reads like a sentence
[ 1, 2, 3 ].each_with_object( [] ) { |e, o| o << e + 1 } # parens make it less readable

Foo = Module.new do     # preferred for a multiline block, other things being equal
  include Comparable
end

Foo = Module.new {      # less preferred
  include Comparable
}

Foo = Module.new { include Comparable }      # preferred for a oneliner
Foo = module.new do include Comparable end   # imo less readable for a oneliner

[ [ 1 ], [ 1, 2 ] ].map { |e| e.map do |e| e + 1 end }  # slightly better
[ [ 1 ], [ 1, 2 ] ].map { |e| e.map { |e| e + 1 } }     # slightly worse

In more complex syntax, such as multiline nested blocks, I try to intersperse {...} and do...end delimiters for most natural result, eg.

Foo = Module.new { 
  if true then
    Bar = Module.new {                          # I intersperse {} and keyword delimiters
      def quux
        "quux".tap do |string|                  # I choose not to intersperse here, because
          puts "(#{string.size} characters)"    # for multiline tap, do ... end in this
        end                                     # case still loks more readable to me.
      end
    }
  end
}

Although the lack of rigid rules might produce sligtly different choices for different programmers, I believe that case-by-case optimization for readability, although subjective, is a net gain over adherence to rigid rules.

Solution 13 - Ruby on-Rails

I took an example of the most voted answer here. Say,

[1,2,3].map do 
  something 
end

If you check what internal implementation in array.rb, map method's header says:

Invokes the given block once for each element of self.

def map(*several_variants)
    (yield to_enum.next).to_enum.to_a
end

i.e. it accepts a block of code - whatever is in between do and end is executed as yield. Then the result is going to be collected as array again, thus it's returning a completely new object.

> So Whenever you encounter a do-end block or { }, just have a > mind map that the block of code is being passed as a parameter, which > will be getting executed internally.

Solution 14 - Ruby on-Rails

There's a third option: Write a preprocessor to infer "end" on its own line, from indentation. The deep thinkers who prefer concise code happen to be right.

Better yet, hack ruby so this is a flag.

Of course, the "pick your fights" simplest solution is to adopt the style convention that a sequence of ends all appear on the same line, and teach your syntax coloring to mute them. For ease of editing, one could use editor scripts to expand/collapse these sequences.

20% to 25% of my ruby code lines are "end" on their own line, all trivially inferred by my indentation conventions. Ruby is the lisp-like language to have achieved the greatest success. When people dispute this, asking where are all the ghastly parentheses, I show them a ruby function ending in seven lines of superfluous "end".

I did code for years using a lisp preprocessor to infer most parentheses: A bar '|' opened a group that autoclosed at the end of the line, and a dollar sign '$' served as an empty placeholder where there would otherwise be no symbols to help infer the groupings. This is of course religious war territory. Lisp/scheme without the parentheses is the most poetic of all languages. Still, it is easier to simply quiet down parentheses using syntax coloring.

I still code with a preprocessor for Haskell, to add heredocs, and to default all flush lines as comments, everything indented as code. I dislike comment characters, whatever the language.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBlake TaylorView Question on Stackoverflow
Solution 1 - Ruby on-RailsPan ThomakosView Answer on Stackoverflow
Solution 2 - Ruby on-RailsDavid BrownView Answer on Stackoverflow
Solution 3 - Ruby on-RailsGStoView Answer on Stackoverflow
Solution 4 - Ruby on-RailsGlyphGryphView Answer on Stackoverflow
Solution 5 - Ruby on-RailsPeter BrownView Answer on Stackoverflow
Solution 6 - Ruby on-RailsDigitalRossView Answer on Stackoverflow
Solution 7 - Ruby on-RailsKelvinView Answer on Stackoverflow
Solution 8 - Ruby on-Railssameera207View Answer on Stackoverflow
Solution 9 - Ruby on-RailshalfbitView Answer on Stackoverflow
Solution 10 - Ruby on-RailsJake KalstadView Answer on Stackoverflow
Solution 11 - Ruby on-RailsShankar RajuView Answer on Stackoverflow
Solution 12 - Ruby on-RailsBoris StitnickyView Answer on Stackoverflow
Solution 13 - Ruby on-RailsVasanth SaminathanView Answer on Stackoverflow
Solution 14 - Ruby on-RailsSyzygiesView Answer on Stackoverflow