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.
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 }
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.
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 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, witheach
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:
http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc
http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace
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
})
Related Topics
Rails - Displaying Foreign Key References in a Form
Ruby Class Instance Variables and Inheritance
CSV - Unquoted Fields Do Not Allow \R or \N (Line 2)
Unable to Load Gem Cocoa Pods While Creating Repo
Are There Better Ways to Prevent 'Yield' When No Block Is Passed In
Heroku: How to Push Seeds.Rb to Existing Rails App
What Do the Fields of Ruby's Gc.Stat Mean
Best Way to Group by Date with Mongoid
Capistrano 3 Execute Within a Directory
Ruby: How to "Require" a File from the Current Working Dir
Deleting While Iterating in Ruby
How to Get a HTML Table Row with Capybara
Rails Sort Tags by Most Used (Tag.Posts.Count)
Print All Method Names of a Class in Ruby
Rails Ssl Issue: (Https://Example.Com) Didn't Match Request.Base_Url (Http://Example.Com)
Rubocop Error 'Class Definition Is Too Long Ruby'