24

Why does this map expression produce different results depending on whether I use braces or do/end?

a = [1,2,3,4,5]


p a.map { |n|
    n*2
}  
#=> [2,4,6,8,10]


p a.map do |n|
    n*2
end  
#=> [1,2,3,4,5]
Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
Grandpa
  • 3,053
  • 1
  • 25
  • 35

3 Answers3

23

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 {.

DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • 1
    @Grandpa It's not particularly about 'p'. It's about the strength/priority between method's taking argument/blocks and {}, do end – sawa Apr 01 '11 at 18:45
  • And how is it possible to use `do...end` properly with a multiline block so that `map` gets it as parameter? Braces - as your suggestion - won't do it, Ruby throws an error, `unexpected keyword_do_block` even the whole block is one line. – karatedog Jul 08 '12 at 14:45
  • @karatedog: You'll probably need to use an intermediate variable here as the parser doesn't support what you want. – DarkDust Jul 09 '12 at 07:31
  • @DarkDust Thanks, but I cannot use an intermediate variable when the Enumerator is infinite because I should have to know beforehand how much items I should generate into that intermediate variable. – karatedog Jul 09 '12 at 15:07
  • I don't get what you mean, of course you can do `foo = a.map do ... end ; p(foo)`. It is the exact same semantics, because `map` always needs to aggregate an array to pass on first. Whether that array is then stored in a variable or passed as parameter to another method makes no difference, the array needs to be created first no matter what. – DarkDust Jul 10 '12 at 08:49
4

That has to do with the difference in associativity of the { character and the do keyword.

In the first case, the block is interpreted as a block argument to the map function. The result of the map function is the argument to the p function.

In the second case, the block is interpreted as a block argument to the p function, while the a.map is interpreted as the first argument to the p function. Since a.map evaluates to a, this prints the original array. The block is effectively ignored in this case.

Confusion
  • 16,256
  • 8
  • 46
  • 71
3

With the do/end syntax you are passing the block to p as a second argument, rather than to the map. You get the same result with:

p a.map

The block is ignored by p as it does not produce anything on inspect.

Douglas F Shearer
  • 25,952
  • 2
  • 48
  • 48