Do..End VS Curly Braces For Blocks in Ruby

do..end vs curly braces for blocks in Ruby

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 }
=> nil
puts [1,2,3].map do |k| k+1; end
=> 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

really means:

task(:rake => pre_rake_task){ something }

And this code:

task :rake => pre_rake_task {

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) {

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.

differing `puts` behavior inside `do..end` blocks and curly braces in Ruby

do...end and {} are 100% semantically equivalent for method blocks. Their only difference is their parsing precedence, so they evaluate differently

To really understand that difference, a few things first.

Ruby lets you call methods without parens:

my_object.my_method my_arg

# so my_arg could actually be a method! Let's put parens in to show that:

Blocks in Ruby are method arguments -- a syntax for passing in closures (except for the special keywords that act in the parent scope). The below two chunks are equivalent:

[1, 2, 3].map { |x| 2 * x }

# split out into two lines
double = ->(x) { 2 * x } # shorthand for `lambda { |x| 2 * x }`
[1, 2, 3].map(&double)

Okay, so knowing all that, let's expose the difference between {} and do...end:

my_method [1, 2, 3].map { |x| 2 * x }

my_method([1, 2, 3].map { |x| 2 * x }) # parses like this

my_method [1, 2, 3].map do |x| 2 * x end

my_method([1, 2, 3].map) do |x| 2 * x end # parses like this

my_method([1, 2, 3].map) { |x| 2 * x } # in other words

{} has more precedence than do...end, immediately getting associated to the method immediately to its left. do...end has lower precedence, and will associate with my_method, which gets passed [1, 2, 3].map and the block as arguments.

This means, what you did above is:

puts(my_array.each) { |num| num *= 2; puts "The new number is #{num}." }

You've passed into puts the my_array.each, which is an enumerator, and a block, and puts does nothing with the blocks passed into it, as do all methods by default.

Does Ruby read do and end the same way as { and }?

No, it doesn't. As a general rule of Ruby, if two things look alike, you can bet that there is a subtle difference between them, which makes each of them unique and necessary.

{ and } do not always stand in the role of block delimiters. When they do not stand in the role of block delimiters (such as when constructing hashes, { a: 1, b: 2 }), they cannot be replaced by do ... end. But when the curly braces do delimit a block, they can almost always be replaced by do ... end. But beware, because sometimes this can change the meaning of your statement. This is because { ... } have higher precedence, they bind tighter than do ... end:

puts [ 1, 2, 3 ].map { |e| e + 1 }         # { ... } bind with #map method
#=> nil

puts [ 1, 2, 3 ].map do |e| e + 1 end # do ... end bind with #puts method
#=> nil

As for the opposite situation, do ... end in the role of block delimiters cannot always be replaced by curly braces.

[ 1, 2, 3 ].each_with_object [] do |e, obj| obj << e.to_s end  # is possible
[ 1, 2, 3 ].each_with_object [] { |e, obj| obj << e.to_s end # invalid syntax

In this case, you would have to parenthesize the ordered argument:

[ 1, 2, 3 ].each_with_object( [] ) { |e, obj| obj << e.to_s }  # valid with ( )

The consequence of these syntactic rules is that you can write this:

[ 1, 2, 3 ].each_with_object [ nil ].map { 42 } do |e, o| o << e end
#=> [ 42, 1, 2, 3 ]

And the above also demonstrates the case where {} are not replaceable by do/end:

[ 1, 2, 3 ].each_with_object [ nil ].map do 42 end do |e, o| o << e end
#=> SyntaxError

Lambda syntax with -> differs slightly in that it equally accepts both:

foo = -> x do x + 42 end  # valid
foo = -> x { x + 42 } # also valid

Furthermore, do and end themselves do not always delimit a block. In particular, this

for x in [ 1, 2, 3 ] do
puts x

and this

x = 3
while x > 0 do
x -= 1

and this

x = 3
until x == 0 do
x -= 1

superficially contains do ... end keywords, but there is no real block between them. The code inside them does not introduce a new scope, it is just syntax used to repeat a few statements. Curly braces cannot be used here, and the syntax could be written without do, such as:

for x in [ 1, 2, 3 ]
puts x

# same for while and until

For the cases where {...} and do...end delimiters are interchangeable, the choice which ones to use is a matter of programming style, and is extensively discussed in another question (from whose accepted answer I took one of my examples here). My personal preference in such case is to emphasize readability over rigid rules.

Using do block vs braces {}

Ruby cookbook says bracket syntax has higher precedence order than do..end

Keep in mind that the bracket syntax
has a higher precedence than the
do..end syntax. Consider the following
two snippets of code:

1.upto 3 do |x|
puts x

1.upto 3 { |x| puts x }
# SyntaxError: compile error

Second example only works when parentheses is used, 1.upto(3) { |x| puts x }

Ruby do/end vs braces

That's because the second line is interpreted as:

p( do ... end

instead of:

p( do ... end)

The grammar is ambiguous in this case and do doesn't seem to bind as strongly as {.

Does using curly braces go against the Ruby way?

While some people go with "braces for one-liners, do-end for multi-liners", I personally find the following rule the most logical:

  • use do-end when your block has side-effects (typically, with each and related methods) and
  • use braces when your block is without side-effects (map, inject and alike)

This logic goes well with method chaining issue that Matt wrote about.

One benefit of this approach is that it is going to make you think about side-effects every time you write a block, and they are very important, although sometimes overlooked by coders with no functional programming background.

Another way to put it, without involving side-effects terminology would be:

  • use do-end for blocks that perform
  • use { and } for blocks that return

Here are couple of articles with more info:

Ruby curly bracket block syntax is not working while do...end works

It's a parsing/precedence issue. Braces try to bind to the nearest token, which is :day in this case, but you want it to bind to every(). You have to write every(:day) { rake 'billing:daily' } to explicitly bind it to the correct token.

Can's use curly braces for it blocks in rspec?

Since do and end are keywords which are not used in any other context except for blocks, the Ruby interpreter doesn't have trouble understanding it. But curly braces are used in at least two different contexts, firstly acting as block delimiters, and secondly acting as delimiters for Hash, which means you have to help the Ruby interpreter a little bit here when using the curly braces to let it know what context you want to use the curly braces in.

In your case, if you use the method syntax with parentheses, i.e. it() instead of it, this should tell the interpreter that the curly braces after the parentheses are meant to be interpreted as delimiters for a block.

Use this...

it('returns true for correct password') { expect ... }

instead of this.

it 'returns true for correct password' { expect ... }

Related Topics

Leave a reply