0

Why does Enumerable#map only support a block while Enumerable#inject supports additional types, e.g. symbols?

I expected the following to work

["1", "2"].map(:to_i).inject(:+)

but only the following works

["1", "2"].map(&:to_i).inject(:+)

Nevertheless, using & for both works too

["1", "2"].map(&:to_i).inject(&:+)

Which one is the best practice and why?

sawa
  • 165,429
  • 45
  • 277
  • 381
AlexN
  • 1,613
  • 8
  • 21

2 Answers2

1

inject looks if a block is given, and if not, it uses the symbol argument instead of a block. With map, this cannot be done because there is a usage of map without a block, which returns an enumerator.

As Sergio comments, it is in principle possible for map to look for an argument (rather than or in addition to looking for a block). The problem is due to the particular implementation that C ruby chose. I suspect it is implemented so because it is much faster to check the existence of a block than to check the arity and the class of an argument.

There is no reason you shouldn't use

["1", "2"].map(&:to_i).inject(:+)

Why not use the convenient symbol argument when it was designed to be used?

sawa
  • 165,429
  • 45
  • 277
  • 381
  • But `map` could be accepting a single argument, a symbol. – Sergio Tulentsev Aug 27 '15 at 09:49
  • @SergioTulentsev That is right, it is in principle possible to do that. The problem is due to the particular implementation that C ruby chose. Cf. sepp2k's comment [here](http://stackoverflow.com/questions/2697024/what-are-and-in-ruby). I suspect it is because it is much faster to check for a block than to check for the arity and the class of an argument. – sawa Aug 27 '15 at 09:51
  • 1
    "There is no reason you shouldn't use" - how about consistency, though, and not having to spend _any_ mental CPU cycles on remembering if this method accepts naked symbols or not? Just use `&:` everythere and you're fine :) – Sergio Tulentsev Aug 27 '15 at 09:59
0

Just check the Enumerable docs

map { |obj| block } → array
map → an_enumerator

and

inject(initial, sym) → obj
inject(sym) → obj
inject(initial) { |memo, obj| block } → obj
inject { |memo, obj| block } → obj

Which one to use from these two

["1", "2"].map(&:to_i).inject(:+)
["1", "2"].map(&:to_i).inject(&:+)

is up to you, but I quickly checked the performance and inject with symbol seems to work faster:

require 'benchmark'

Benchmark.bm do |x|
  x.report { ([1]*1_000_000).map(&:to_i).inject(:+) }
  x.report { ([1]*1_000_000).map(&:to_i).inject(&:+) }
end

    user     system      total        real
0.120000   0.010000   0.130000 (  0.122907)
0.140000   0.000000   0.140000 (  0.146640)
Rustam Gasanov
  • 15,290
  • 8
  • 59
  • 72