What is the difference or value of these block coding styles in Ruby?
The rails team and many other rubyists prefer to use curly braces for one line blocks and do...end
for multi-line ones.
The only functional difference between the two is that the precedence of a do...end
block is lower than that of a {...}
block.
Different behaviour of 'do .. end' and {..} block in ruby
sawa's answer is correct, but as the OP has asked for more clarification, I'm supplying my own answer.
All four of these method calls behave the same, passing a block to a foo
method:
foo { ... }
foo do ... end
foo() { ... }
foo() do ... end
When you write two methods and a block, without parentheses around the arguments, it is unclear which method the block goes with:
foo bar { ... }
foo bar do ... end
The question is: "Am I passing a block to bar
, and then passing its return value to foo
? Or am I calling foo
with bar
as an argument and also passing along a block?"
With parentheses, you can make this clear using either block style:
# Passing a block to `bar`, and then passing the result to `foo`
foo( bar { ... } )
foo( bar do ... end )
# Passing an argument and block to `foo`
foo( bar ) { ... }
foo( bar ) do ... end
The difference between {…}
and do…end
that you have run into is where Ruby chooses to put the parentheses when you omit them. The two block notations have different precedence, and so you end with different results:
# Passing a block to `bar`, and then passing the result to `foo`
foo bar{ ... }
foo( bar do ... end )
# Passing an argument and block to `foo`
foo bar do ... end
foo( bar ){ ... }
So, specifically in your case:
# This code…
p a.sort do |x,y|
y <=> x
end
# …is the same as this code:
b = a.sort
p(b){ |x,y| y <=> x }
# Note that you are passing a block to the `p` method
# but it doesn't do anything with it. Thus, it is
# functionally equivalent to just:
p a.sort
And,
# This code…
p a.sort { |x,y|
y <=> x
}
# …is functionally the same as this code:
b = a.sort{ |x,y| y <=> x }
p b
Finally, if you still don't get it, perhaps deeply considering the following code and output will help:
def foo(a=nil)
yield :foo if block_given?
end
def bar
yield :bar if block_given?
:bar_result
end
foo bar { |m| puts "block sent to #{m}" }
#=> block sent to bar
#=> foo got :bar_result
foo( bar do |m| puts "block sent to #{m}" end )
#=> block sent to bar
#=> foo got :bar_result
foo( bar ){ |m| puts "block sent to #{m}" }
#=> foo got :bar_result
#=> block sent to foo
foo bar do |m| puts "block sent to #{m}" end
#=> foo got :bar_result
#=> block sent to foo
Notice that the first and last examples in the code immediately above differ only in whether they use {…}
or do…end
for the block.
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
Why does Ruby evaluate these two expressions differently? The only difference is {...} and do...end
Precedence matters. {}
has a higher precedence over method call. do
-end
has a lower precedence.
pp [1, 2, 3].map { |r| r + 1 }
is parsed as pp ([1, 2, 3].map { |r| r + 1 })
, which is:
pp([1, 2, 3].map { |r| r + 1 })
pp [1, 2, 3].map do |r| r + 1 end
is parsed as (pp [1, 2, 3].map) do |r| r + 1 end
, which is:
pp([1, 2, 3].map, &->(r){ r + 1 })
In the latter case passing block to pp
is a NOOP
.
Weird imoperfection in Ruby blocks
It doesn't think it's a hash - it's a precedence issue. {}
binds tighter than do end
, so method :argument { other_method }
is parsed as method(:argument {other_method})
, which is not syntactically valid (but it would be if instead of a symbol the argument would be another method call).
If you add parentheses (method(:argument) { other_method }
), it will work fine.
And no, the code is not actually valid. If it were, it would work.
Why does a do/end block behave/return differently when a variable is assigned?
puts [1,2,3].map do |x|
x + 1
end.inspect
Is parsed as:
puts([1,2,3].map) do |x|
x + 1
end.inspect
I.e. map
is called without a block (which will make it return the unchanged array in 1.8 and an enumerator in 1.9) and the block is passed to puts (which will just ignore it).
The reason that it works with {}
instead of do end
is that {}
has different precedence so it's parsed as:
puts([1,2,3].map { |x| x + 1 }.inspect)
Similarly the version using a variable works because in that case there is simply no ambiguity.
Related Topics
How to "Pretty" Format Json Output in Ruby on Rails
Difference Between Collection Route and Member Route in Ruby on Rails
Using Rails Serialize to Save Hash to Database
How to Get a Single Character Without Pressing Enter
Why Are Gems Installed in a Directory With a Different Ruby Version Than I'M Running
Method to Parse HTML Document in Ruby
Creating Microsoft Word (.Docx) Documents in Ruby
Is 'Eval' Supposed to Be Nasty
Best Practices With Stdin in Ruby
Is There a "Do ... While" Loop in Ruby
Which Ruby on Rails Is Compatible With Which Ruby Version