Difference or Value of These Block Coding Styles in Ruby

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



Leave a reply



Submit