Ruby procs and collections

In Ruby, we can use Enumerable#map to process collections. The map method takes a block:

1 names = %w(ant)
2 
3 names.map{ |x| x.upcase }

We can also pass in custom objects which respond to to_proc:

1 class Double
2   def to_proc
3     proc{ |n| n * 2 }
4   end
5 end
6 
7 arr = [1.0, 2.0, 3.0]
8 arr.map(&Double.new) # => [2.0, 4.0, 6.0]

In Ruby 2.3+, Hash has a to_proc method which means we can pass a hash into a collection:

1 h = { foo: 1, bar: 2, baz: 3 }
2 
3 [:foo, :bar].map(&h) #=> calls h.to_proc

I decided to do a quick benchmark on how efficient the block method of map runs compared to the using a proc:

The results show that using proc is slower than calling map with a block:

 1 #/usr/bin/env ruby
 2 
 3 require "benchmark/ips"
 4 
 5 arr = [1.0, 2.0, 3.0]
 6 
 7 h = { foo: 1, bar: 2, baz: 3 }
 8 
 9 class Double
10   def to_proc
11     proc{ |n| n * 2 }
12   end
13 end
14 
15 Benchmark.ips do |x|
16   x.report("arr.map{ |x| x*2 }"){
17     arr.map{ |x| x*2 }
18   }
19 
20   x.report("arr.map(&Double.new)"){
21     arr.map(&Double.new)
22   }
23 
24   x.report("[:foo, :bar].map"){
25     [:foo, :bar].map{ |x| h[x] }
26   }
27 
28   x.report("[:foo, :bar].map(&h)"){
29     [:foo, :bar].map(&h)
30   }
31 
32   x.compare!
33 end
Warming up --------------------------------------
  arr.map{ |x| x*2 }    94.558k i/100ms
arr.map(&Double.new)    40.587k i/100ms
    [:foo, :bar].map   111.149k i/100ms
[:foo, :bar].map(&h)    71.085k i/100ms
Calculating -------------------------------------
  arr.map{ |x| x*2 }      1.740M (± 5.6%) i/s -      8.699M in   5.015954s
arr.map(&Double.new)    591.703k (± 5.8%) i/s -      2.963M in   5.024683s
    [:foo, :bar].map      1.919M (±15.7%) i/s -      9.225M in   5.049601s
[:foo, :bar].map(&h)    995.047k (±15.0%) i/s -      4.834M in   5.029552s

Comparison:
    [:foo, :bar].map:  1919075.6 i/s
  arr.map{ |x| x*2 }:  1739722.6 i/s - same-ish: difference falls within error
[:foo, :bar].map(&h):   995046.9 i/s - 1.93x  slower
arr.map(&Double.new):   591703.4 i/s - 3.24x  slower

From the results, I learnt that using dynamic procs may be more suited for smaller collections with map. For larger collections, it is more efficient to stick to the block method.

Happy Hacking and stay curious!!