views:

125

answers:

2

The following question is related to the question "Ruby Print Inject Do Syntax". My question is, can we insist on using do and end and make it work with puts or p?

This works:

a = [1,2,3,4]

b = a.inject do |sum, x|
  sum + x
end
puts b   # prints out 10

so, is it correct to say, inject is an instance method of the Array object, and this instance method takes a block of code, and then returns a number. If so, then it should be no different from calling a function or method and getting back a return value:

b = foo(3)
puts b

or

b = circle.getRadius()
puts b

In the above two cases, we can directly say

puts foo(3)
puts circle.getRadius()

so, there is no way to make it work directly by using the following 2 ways:

a = [1,2,3,4]

puts a.inject do |sum, x|
  sum + x
end

but it gives

ch01q2.rb:7:in `inject': no block given (LocalJumpError)
        from ch01q2.rb:4:in `each'
        from ch01q2.rb:4:in `inject'
        from ch01q2.rb:4

grouping the method call using ( ) doesn't work either:

a = [1,2,3,4]

puts (a.inject do |sum, x| 
        sum + x   
      end)

and this gives:

ch01q3.rb:4: syntax error, unexpected kDO_BLOCK, expecting ')'
puts (a.inject do |sum, x|
                 ^
ch01q3.rb:4: syntax error, unexpected '|', expecting '='
puts (a.inject do |sum, x|
                          ^
ch01q3.rb:6: syntax error, unexpected kEND, expecting $end
      end)
         ^

finally, the following version works:

a = [1,2,3,4]

puts a.inject { |sum, x|
    sum + x
}

but why doesn't the grouping of the method invocation using ( ) work in the earlier example? What if a programmer insist that he uses do and end, can it be made to work?

+11  A: 

From the (unofficial) ruby grammar, we see that the contents of (...) in puts (...) must be CALL_ARGS, which don't directly reduce to STMT. However, they can reduce to '(' COMPSTMT ')'. By including an extra set of parentheses, you can use do ... end.

a = [1,2,3,4]

puts ((a.inject do |sum, x| 
         sum + x   
       end))
outis
it does work. it may come off as strange to me at first as Ruby's parenthesis is sometimes optional. And also, have 2 sets of `( )` may come off as, if one pair doesn't work, add an extra pair.
動靜能量
+2  A: 

The issue here isn't just your parentheses: it's primarily the space after puts before the parentheses.

With the code

a = [1,2,3,4]

puts (a.inject do |sum, x|
             sum + x
                    end)

We get the syntax errors you listed in the question.

If you drop the space after puts,

a = [1,2,3,4]

puts(a.inject do |sum, x|
             sum + x
                    end)

prints out 10 as expected.

Finally, using puts ((a.inject... with the space and double parentheses also prints out 10, but running that through ruby -cw XXX.rb tells us:

a.rb:5: warning: (...) interpreted as grouped expression

Syntax OK

ruby -cw is used to Check the syntax with full Warnings turned on. When -cw is on, you will be warned about dubious parentheses and grouping. The error I'm more used to seeing is "don't put space before argument parentheses" -- so don't do that either!

Lastly, the reason a.inject do fails without parentheses but a.inject { works, is that braces have a higher precedence than do/end. As a very rough guideline, you could say that p a.map { foo } is equivalent to p(a.map do foo end); and p a.map do foo end is equivalent to (p a.map) do foo end, which of course does not take a block argument.

See also the Ruby quick reference on blocks (particularly the last two lines):

Blocks, Closures, and Procs

Blocks/Closures

  • blocks must follow a method invocation:

invocation do ... end

invocation { ... }

  • Blocks remember their variable context, and are full closures.
  • Blocks are invoked via yield and may be passed arguments.
  • Brace form has higher precedence and will bind to the last parameter if invocation made w/o parens.
  • do/end form has lower precedence and will bind to the invocation even without parens.
Mark Rushakoff