The Ruby Documentation says that "do/end is equivalent to curly braces", so why is it that when I attempt to do the following I do not receive any output:

a = [1, 2, 3]
for i in a {
  puts i
}

When I perform the above I receive no output (but I don't receive an error message either). However, when I do the following everything is as it should be:

a = [1, 2, 3]
for i in a do
  puts i
end
#=> 1
#=> 2 
#=> 3

I know this can be done more idiomatically with the each statement, but that's not what I'm asking. What am I not understanding here?

2 Answers

3
Jörg W Mittag On Best Solutions

The Ruby Documentation says that "do/end is equivalent to curly braces"

No, it doesn't. It says (bold emphasis mine):

In this context, do/end is equivalent to curly braces […]

What "in this context" means is defined directly before the half-sentence you quoted:

do

Paired with end, can delimit a code block

So, "in this context" here refers to the context of a block.

so why is it that when I attempt to do the following I do not receive any output

Because this is a completely different context, again quoting from the documentation you linked to:

do can also (optionally) appear at the end of a for/in statement. (See for for an example.)

The "also" in that sentence makes it very clear that this is a different usage of the keyword do that has nothing to do with the usage discussed in this section. And if you look at the documentation of for, you can see that there is no mention of curly braces being allowed.

When I perform the above I receive no output (but I don't receive an error message either).

That is not true. Your code is syntactically invalid because it is missing the end keyword to end the for/in expression, therefore you get a "syntax error, unexpected end-of-input" on line 4:

ruby -c -e 'a = [1, 2, 3]
for i in a {
  puts i
}'
# -e:4: syntax error, unexpected end-of-input

And if you add the missing end, you get a in `<main>': undefined method `a' for main:Object (NoMethodError) on line 2:

ruby -e 'a = [1, 2, 3]
for i in a {
  puts i
}
end'
# -e:2:in `<main>': undefined method `a' for main:Object (NoMethodError)

Again, this is expected because curly braces delimit a code block, so

a {
  puts i
}

is interpreted as a code block being passed to a and since variables cannot receive arguments, only methods can, a must be a method. Therefore, Ruby rightfully complains about not finding a method named a.

There are three ways of delimiting the iterator expression from the loop body expression in a for/in loop (and the same applies to while and until loops, actually):

  1. An expression separator. An expression separator can either be

    1. a semicolon ;
    2. a newline
  2. The keyword do

So, the following would all be valid fixes for your code:

# non-idiomatic
for i in a; puts i end

# non-idiomatic
for i in a
puts i end

# the same but with idiomatic indentation and whitespace
for i in a
  puts i
end

# idiomatic
for i in a do puts i end

# redundant, non-idiomatic
for i in a do
  puts i
end

Note, that when I say "idiomatic" above, that is to be interpreted relative, since actually for/in loops as a whole are completely non-idiomatic, and you would rather do this:

a.each do |i|
  puts i
end

or maybe

a.each(&method(:puts))

It is in general preferred to not mix I/O and data transformation, so another idiomatic solution would be to transform the data to the desired output first, then output it, like this:

puts a.join("\n")

Except that Kernel#puts will already treat Array arguments special and print each element on its own line (as documented at IO#puts), so the real correct idiomatic solution for your code would be just:

puts a
0
iGian On

Take a look to the documentation here: For loop It states:

Like while and until, the do is optional. The for loop is similar to using each, but does not create a new variable scope.

And also

The for loop is rarely used in modern ruby programs.

So, be less Pythonic :) using Enumerator#each instead:

a.each { |a| puts a }