In Ruby, you'll often see code like this:
numbers = [1, 2, 3]
strings = numbers.map(&:to_s)
# => ["1", "2", "3"]
This is equivalent to:
strings = numbers.map { |n| n.to_s }
But it's shorter. The &:to_s syntax converts a symbol into a block, calling the to_s method on each element.
How It Works
The & operator, when applied to a symbol in a method argument, does two things:
- Calls
to_procon the symbol - Passes the resulting proc as a block to the method
Symbols in Ruby have a to_proc method that returns a proc. That proc calls the method named by the symbol on whatever object it receives:
:to_s.to_proc # Returns a proc equivalent to: ->(obj) { obj.to_s }
When you write .map(&:to_s), Ruby expands it to:
.map(&:to_s.to_proc)
# Which becomes:
.map { |obj| obj.to_s }
The Symbol#to_proc Method
Under the hood, Symbol#to_proc is defined roughly like this:
class Symbol
def to_proc
->(obj, *args) { obj.send(self, *args) }
end
end
It creates a lambda that calls send on the object with the symbol as the method name. If you pass :to_s, the lambda calls obj.send(:to_s), which is obj.to_s.
Common Uses
Converting types:
[1, 2, 3].map(&:to_s) # ["1", "2", "3"]
["1", "2", "3"].map(&:to_i) # [1, 2, 3]
Calling methods on collections:
words = ["hello", "world"]
words.map(&:upcase) # ["HELLO", "WORLD"]
words.map(&:length) # [5, 5]
Filtering with select:
[1, nil, 2, nil, 3].compact # [1, 2, 3]
# Or using select:
[1, nil, 2, nil, 3].select(&:itself) # [1, 2, 3]
Checking boolean methods:
strings = ["", "hello", "", "world"]
strings.select(&:empty?) # ["", ""]
strings.reject(&:empty?) # ["hello", "world"]
When It Doesn't Work
Symbol-to-proc only works for methods that take no arguments (or have default arguments):
# Works - no arguments
[1, 2, 3].map(&:to_s)
# Doesn't work - requires an argument
[1, 2, 3].map(&:+) # SyntaxError
For methods needing arguments, use a block:
[1, 2, 3].map { |n| n + 10 } # [11, 12, 13]
Chaining Method Calls
You can't chain methods with symbol-to-proc directly:
# Doesn't work
words.map(&:upcase.reverse) # SyntaxError
# Works - use a block
words.map { |w| w.upcase.reverse }
Symbol-to-proc is limited to a single method call per symbol.
Performance Considerations
Symbol-to-proc has slight overhead from calling to_proc and creating the lambda. For tight loops over large collections, explicit blocks might be marginally faster:
# Explicit block - slightly faster
large_array.map { |n| n.to_s }
# Symbol-to-proc - cleaner
large_array.map(&:to_s)
In practice, the difference is negligible. Readability usually wins.
The & Operator in Context
The & operator has multiple uses in Ruby:
Converting symbol to proc:
[1, 2, 3].map(&:to_s)
Converting proc to block:
my_proc = ->(n) { n * 2 }
[1, 2, 3].map(&my_proc) # [2, 4, 6]
Capturing block as proc:
def my_method(&block)
block.call # Call the block as a proc
end
my_method { puts "Hello" }
In method definitions, &block captures the block as a proc. In method calls, &symbol converts the symbol (or proc) to a block.
When to Use Symbol-to-Proc
Use it when:
- You're calling a single method with no arguments
- The intent is clear from the symbol name
- You want concise, idiomatic Ruby
Don't use it when:
- The method needs arguments
- You need to chain methods
- The logic is complex enough that a block is clearer
Alternatives and Variations
Ruby 2.6 introduced composition with >>:
add_one = ->(n) { n + 1 }
double = ->(n) { n * 2 }
composed = add_one >> double
[1, 2, 3].map(&composed) # [4, 6, 8]
You can also define custom to_proc methods on your own classes:
class Multiplier
def initialize(factor)
@factor = factor
end
def to_proc
->(n) { n * @factor }
end
end
multiplier = Multiplier.new(3)
[1, 2, 3].map(&multiplier) # [3, 6, 9]
Historical Context
Symbol-to-proc was introduced in Ruby 1.9. Before that, you'd see this pattern implemented in libraries like ActiveSupport:
# Pre-1.9 with ActiveSupport
[1, 2, 3].map(&:to_s) # Required ActiveSupport
# Ruby 1.9+
[1, 2, 3].map(&:to_s) # Built into language
It became so popular in Rails that it was adopted into core Ruby.
Related Patterns
Symbol-to-proc is part of Ruby's broader functional programming features:
# Method references
method_ref = :to_s.to_proc
[1, 2, 3].map(&method_ref)
# Partial application with curry
add = ->(a, b) { a + b }
add_five = add.curry.(5)
[1, 2, 3].map(&add_five) # [6, 7, 8]
Reading Symbol-to-Proc Code
When you see &:method_name, read it as "call method_name on each element":
users.map(&:email) # "Get the email of each user"
items.select(&:valid?) # "Select items that are valid"
records.reject(&:empty?) # "Reject records that are empty"
Further Reading
The Ruby documentation on Symbol#to_proc explains the implementation.
For deeper understanding of procs and lambdas, see the Proc class documentation.
The book Eloquent Ruby by Russ Olsen has a chapter on blocks and procs that covers symbol-to-proc in the context of idiomatic Ruby.
Symbol-to-proc is quintessentially Ruby—concise, expressive, and a little magical.
0 comments