Array#to_proc: A complement to Symbol#to_proc

Posted by sjs
on Thursday, May 03

By now many Ruby programmers are familiar with the magic that is Symbol#to_proc.

If you haven’t seen anything such as youngest = people.map(&:age).min yet here’s a quick explanation of the syntax.

  • Ruby calls to_proc on the block parameter if it is not already a Proc. (coercion)
  • Since a symbol is a message, Symbol#to_proc simply constructs a block that sends self (the symbol) to the first block parameter.
  • The code above is equivalent to youngest = people.map { |p| p.age }.min
  • The few characters saved are significant if you are sufficiently lazy, or if you frequently chain short blocks like that together.

This is a great way to clean up your code, and the brevity will have you groaning when you have to type out something such as: birth_years = people.map { |p| p.birthdate.year }

But there is a simple way to make that shorter using a trick similar to Symbol#to_proc. To me it seems logical that if map(&:sym) sends :sym to each element of the enumerable object, then an array of symbols should chain the calls together on each element of the enumerable.

If you’ve looked at the implementation of Symbol#to_proc in Rails then this should look familiar as it’s nearly the same code.

1
2
3
4
5
6
7
8
9
10
11
module ArrayExtensions
  # Turns an array of symbols into a simple proc, which is especially useful for enumerations. Examples:
  #
  #   # The same as people.map { |p| p.birthdate.year }
  #   people.map(&[:birthdate, :year])
  def to_proc
    Proc.new do |*args|
      self.inject(args.shift) { |obj, msg| obj = obj.__send__(msg, *args) }
    end
  end
end

This makes the following code valid, and equivalent to the previous example using birth_years.


birth_years = people.map(&[:birthdate, :year])

You can use strings, like so: birth_years = people.map(&%w[birthdate year]), but I don’t dig the &% part of it. To each their own.

Then all that needs to be done is load the extension. I’m doing all this in the context of Rails so I’m setting this up in my environment.rb.


Array.send(:include, ArrayExtensions)
Comments

Leave a response