Ruby procs and collections

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

1
2
3
names = %w(ant)

names.map{ |x| x.upcase }

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

1
2
3
4
5
6
7
8
class Double
  def to_proc
    proc{ |n| n * 2 }
  end
end

arr = [1.0, 2.0, 3.0]
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
2
3
h = { foo: 1, bar: 2, baz: 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#/usr/bin/env ruby

require "benchmark/ips"

arr = [1.0, 2.0, 3.0]

h = { foo: 1, bar: 2, baz: 3 }

class Double
  def to_proc
    proc{ |n| n * 2 }
  end
end

Benchmark.ips do |x|
  x.report("arr.map{ |x| x*2 }"){
    arr.map{ |x| x*2 }
  }

  x.report("arr.map(&Double.new)"){
    arr.map(&Double.new)
  }

  x.report("[:foo, :bar].map"){
    [:foo, :bar].map{ |x| h[x] }
  }

  x.report("[:foo, :bar].map(&h)"){
    [:foo, :bar].map(&h)
  }

  x.compare!
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!!