One pretty well-know idiom in Ruby, and Facets, is Symbol.to_proc. It lets you turn these:

1 [1, 2, 3].map { |num| num.next }  #=> [2, 3, 4]
2 
3 %w[alpha beta gamma].map { |word| word.upcase }
4 #=> ["ALPHA", "BETA", "GAMMA"]

…into these:

1 [1, 2, 3].map(&:next)
2 %w[alpha beta gamma].map(&:upcase)

It’s a nice little trick, though it’s not to everyone’s taste. If you’re already comfortable with Symbol.to_proc, you can skip down to the Class.to_proc section. But if you’re not, it’s worth a minute of your attention to learn it. Read on…

How it’s done

When a method takes a block, you can call yield, to run the block.

1 def with_a_block(a_param)
2     yield
3 end
4 with_a_block('param') {
5     puts 'in the block'
6 }

Or, you can name the block as the last parameter to the method, and put an ampersand in front of it. The ampersand makes ruby convert the block to a procedure, by calling to_proc on it. (So any object with a to_proc method can work this way, if you want.) This example works just like the last one:

1 def named_block(a_param, &blk)
2     blk.call
3 end
4 named_block('my_param') {
5     puts 'in the named block'
6 }

Symbol’s to_proc method creates a procedure that takes one argument, and sends the symbol to it. Sending a symbol to an object is the same as calling a method on it: object.send(:method) works the same as object.method. In the earlier upcase example, each word is passed to a procedure that calls upcase on it, giving us a list of uppercased strings.

1 &:upcase
2 # becomes...
3 lambda { |obj|
4     obj.send(:upcase)
5 }
6 # or...
7 lambda { |obj|
8     obj.upcase
9 }

Class.to_proc

So Symbol.to_proc creates a function that takes an argument, and calls that method on it. Class.to_proc creates a function that passes its argument to its constructor, yielding an instance of itself. This is a welcome addition to the to_proc family.

 1 require 'facets'
 2 
 3 class Person
 4     def initialize(name)
 5         @name = name
 6     end
 7 end
 8 names = %w[mickey minney goofy]
 9 characters = names.map(&Person)
10 
11 puts characters.inspect
12 
13 &Person
14 # becomes...
15 lambda { |obj|
16     Person.new(obj)
17 }

Why it’s nice

  • It’s fewer characters – it semantically compresses your code.
  • It lets you think, makes you think, on a higher level. You think about operations on your data, rather than handling one item at a time. It raises your level of thinking.
  • It works with first-class functions, which are worth understanding. They give you new ways to elegantly solve some problems (well, new to some audiences). They’re not fringe anymore – they’ve been in C# since v2.0.