Block Definition - Difference Between Braces and Do-End

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
end

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

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

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

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:
my_object.my_method(my_arg())

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.

Ruby do/end vs braces

That's because the second line is interpreted as:

p(a.map) do ... end

instead of:

p(a.map do ... end)

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

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

Basic ruby question: the { } vs do/end construct for blocks

In the "not working" case, the block is actually attached to the puts call, not to the collect call. {} binds more tightly than do.

The explicit brackets below demonstrate the difference in the way Ruby interprets the above statements:

puts(test = [1, 1, 1].collect) do |te|
te + 10
end

puts test = ([1, 1, 1].collect {|te|
te + 10
})

Block not called in Ruby

Add parentheses to puts

puts(m do
x = 2
y = 3
x * y
end)

The output is 6.

Your code is equivalent to

puts(m) do 
x = 2
y = 3
x * y
end


Related Topics



Leave a reply



Submit