Or: Pithy Procs for Working with Enumerables

Typing out code blocks every time you want to use map or each is a bit of a pain, and often unnecessary.

You may know that ruby(1..5).map { |n| n.to_s } can be rewritten as (1..5).map &:to_s.

But what does the latter really mean?

The ampersand lets Ruby know you’re passing something to be used as a block and not an argument. It also calls #to_proc on the something. Symbol#to_proc is implemented in C, but for our purposes, it maps to something roughly like this:

def to_proc
  proc{ |caller| caller.send(self) }
end

Here’s another example:

(1..5).each { |n| puts(n) } can be rewritten as (1..5).each &method(:puts).

Method#to_proc is also written in C, but the documentation includes a ruby equivalent:

def to_proc
  proc{|*args|
    self.call(*args)
  }
end

What about something like (1..5).map { |n| n * 2 }? (1..5).map &2.method(:*).

What else can we pass in? Anything with to_proc. Lambdas? Check.

foo = ->(n, i) { puts "#{i}: #{n}" }
(1..5).each_with_index &foo

Implementing #to_proc

Why not add a to_proc method to a command class?

class PurchaseCommand
  attr_accessor :shipment

  def initialize(customer)
    @shipment = Shipment.new(customer)
  end

  def call(item)
    item.deduct_inventory
    shipment.items << item
   end

  def to_proc
    proc{ |item| self.call(item) }
  end
end

def make_purchase
  ...
  cart_items.each &PurchaseCommand.new(customer)
  ...
end

In the above, only one PurchaseCommand is initialized; the same command is used for all items in the cart. It might be a bit clearer to write it this way:

cmd = PurchaseCommand.new(customer)
cart_items.each &cmd

This also allows us to hold a reference to the command for later use, if, for example we wanted do something with its shipment.

Not just for map and each!

This isn’t limited to each and map.

(1..10).partition &:odd?

Be careful, though. While arr.reject &:nil? is shorter than arr.reject { |i| i.nil? }, arr.compact is shorter still, and arguably more intention-revealing. At least if you know your enumerable methods.