5

There are two different syntax for writing blocks in Ruby. There's

do |something|
    ...
end

and there's also

{ |something|
    ...
}

I realized that what this is doing is just replacing the keywords "do" and "end" with brackets. This made me wonder, when Ruby works with these two syntax, is there any significant difference in the way it handles them. Or does Ruby handle them the same way, interchangeably? If they're totally interchangeable, then why include both?

Michael Nail
  • 101
  • 8
  • 1
    possible duplicate of [do..end vs curly braces for blocks in Ruby](http://stackoverflow.com/questions/5587264/do-end-vs-curly-braces-for-blocks-in-ruby) – falsetru Jul 29 '14 at 03:49
  • 1
    @falsetru, this is not a duplicate. This question asks whether (and when) are `do`...`end` and `{`...`}` are interchangeable. The question you link is concerned with style, it asks which ones to use when they _are_ interchangeable. – Boris Stitnicky Jul 29 '14 at 06:08

2 Answers2

9

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
2
3
4
#=> nil

puts [ 1, 2, 3 ].map do |e| e + 1 end      # do ... end bind with #puts method
#<Enumerator:0x0000010a06d140>
#=> 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 }   # 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
end

and this

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

and this

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

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
end

# 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.

Til
  • 5,150
  • 13
  • 26
  • 34
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
0

Generally, the { } block syntax is used for single-line blocks, and the do ... end syntax is used for multi-line blocks. One difference between the two is that your parameters must be inside parenthesis for curly-brace blocks. Example:

some_method(param1, param2) { ... }

some_method param1, param2 do
   ...
end
August
  • 12,410
  • 3
  • 35
  • 51
  • `"123".gsub /./ do |s| (s.to_i + 1).to_s end => "234"`, but `"123".gsub /./ { |s| (s.to_i + 1).to_s }` raises a syntax error. On the other hand, `[1,2,3].each_with_object [] { |e,a| a << e + 1 } => [2, 3, 4]`. Know why? – Cary Swoveland Jul 29 '14 at 04:45
  • @CarySwoveland No idea, can't find it in the block spec either. – August Jul 29 '14 at 04:53
  • Because Ruby parser needs some hint for whether `{` is beginning of a hash or a block. Ruby parser tries to parse `some_method p1, p2, {...}` as a hash param, not block; on the other end, it treats `some_method {...}` as block param. – huocp Jul 29 '14 at 05:32