Ruby Procs
Flexible anonymous functions stored as objects
🔮 What are Ruby Procs?
Procs (procedures) are blocks wrapped in objects that can be stored, passed around, and reused. They're more flexible than lambdas with lenient argument checking and block-like return behavior.
# Create a Proc
greet = Proc.new { |name| puts "Hello, #{name}!" }
# Call the Proc
greet.call("Alice")
# Shorter with proc method
square = proc { |x| x * x }
puts square.call(5)
Output:
Hello, Alice!
25
Key Proc Features
Object
Procs are reusable objects
my_proc = Proc.new { |x| x * 2 }
my_proc.call(5)
Flexible Args
Lenient with argument count
p = Proc.new { |a, b| a }
p.call(1) # Works, b is nil
Return
Returns from enclosing method
p = Proc.new { return 42 }
# Returns from method
Block Conversion
Convert blocks to Procs with &
def run(â–ˆ)
block.call
end
🔹 Creating Procs
Ruby offers multiple ways to create Procs: Proc.new, the proc method, and converting blocks with &. All create Proc objects, but Proc.new is most explicit while proc is more concise for quick definitions.
# Using Proc.new
greet = Proc.new { |name| "Hello, #{name}!" }
puts greet.call("Bob")
# Using proc method
multiply = proc { |a, b| a * b }
puts multiply.call(4, 5)
# Multi-line Proc
calculate = Proc.new do |x, y|
sum = x + y
product = x * y
"Sum: #{sum}, Product: #{product}"
end
puts calculate.call(3, 4)
# Proc without parameters
say_hi = proc { "Hi there!" }
puts say_hi.call
Output:
Hello, Bob!
20
Sum: 7, Product: 12
Hi there!
🔹 Calling Procs
Like lambdas, Procs can be invoked using .call(), square brackets [], or parentheses. All methods work identically, giving you flexibility in how you execute your Proc objects throughout your code.
# Create a Proc
double = proc { |x| x * 2 }
# Method 1: .call()
puts double.call(5)
# Method 2: Square brackets
puts double[6]
# Method 3: Parentheses
puts double.(7)
# With multiple arguments
add = proc { |a, b, c| a + b + c }
puts add.call(1, 2, 3)
puts add[10, 20, 30]
Output:
10
12
14
6
60
🔹 Flexible Argument Handling
Unlike lambdas, Procs don't enforce strict argument counts. Missing arguments become nil, and extra arguments are ignored. This flexibility makes Procs behave more like blocks, useful when argument counts vary.
# Proc with flexible arguments
greet = proc { |name, age| "#{name} is #{age || 'unknown'} years old" }
# Works with correct arguments
puts greet.call("Alice", 25)
# Works with missing arguments (age becomes nil)
puts greet.call("Bob")
# Works with extra arguments (ignored)
puts greet.call("Charlie", 30, "extra")
# Multiple parameters
display = proc { |a, b, c| "a=#{a}, b=#{b}, c=#{c}" }
puts display.call(1)
puts display.call(1, 2)
puts display.call(1, 2, 3, 4, 5)
Output:
Alice is 25 years old
Bob is unknown years old
Charlie is 30 years old
a=1, b=, c=
a=1, b=2, c=
a=1, b=2, c=3
🔹 Converting Blocks to Procs
The ampersand (&) operator converts blocks to Proc objects in method parameters. This allows methods to capture blocks as named variables, enabling you to store, pass, or call them multiple times.
# Capture block as Proc
def run_twice(â–ˆ)
puts "First run:"
block.call
puts "Second run:"
block.call
end
run_twice { puts "Hello!" }
# Use Proc in method
def apply_operation(a, b, &operation)
result = operation.call(a, b)
puts "Result: #{result}"
end
apply_operation(5, 3) { |x, y| x + y }
apply_operation(10, 2) { |x, y| x - y }
# Store block as Proc
def save_block(â–ˆ)
@saved_proc = block
end
save_block { puts "Saved!" }
@saved_proc.call
Output:
First run:
Hello!
Second run:
Hello!
Result: 8
Result: 8
Saved!
🔹 Procs vs Lambdas
Procs and lambdas differ in argument checking and return behavior. Procs are lenient with arguments and return from the enclosing method, while lambdas are strict and return to the caller, making each suitable for different scenarios.
# Argument handling difference
my_proc = proc { |a, b| "a=#{a}, b=#{b}" }
my_lambda = lambda { |a, b| "a=#{a}, b=#{b}" }
puts "Proc with 1 arg:"
puts my_proc.call(1) # Works, b is nil
puts "\nLambda with 1 arg:"
# my_lambda.call(1) # Would raise ArgumentError
# Check if it's a lambda
puts "\nIs Proc a lambda? #{my_proc.lambda?}"
puts "Is Lambda a lambda? #{my_lambda.lambda?}"
# Return behavior (in method context)
def test_return
my_proc = proc { return "Proc return" }
my_lambda = lambda { return "Lambda return" }
# Lambda returns to this method
result = my_lambda.call
puts result
# Proc would return from test_return method
# my_proc.call # Would exit test_return immediately
"Method end"
end
puts test_return
Output:
Proc with 1 arg:
a=1, b=
Lambda with 1 arg:
Is Proc a lambda? false
Is Lambda a lambda? true
Lambda return
Method end
🔹 Practical Proc Examples
Procs excel in callback systems, event handlers, and configuration. Their flexibility with arguments and ability to be stored make them ideal for scenarios where you need reusable code blocks with varying inputs.
# Callback system
class EventHandler
def initialize
@callbacks = []
end
def on_event(&callback)
@callbacks << callback
end
def trigger_event(data)
@callbacks.each { |callback| callback.call(data) }
end
end
handler = EventHandler.new
handler.on_event { |data| puts "Handler 1: #{data}" }
handler.on_event { |data| puts "Handler 2: #{data.upcase}" }
handler.trigger_event("hello")
# Data transformation
transformers = [
proc { |x| x * 2 },
proc { |x| x + 10 },
proc { |x| x ** 2 }
]
value = 5
transformers.each do |transformer|
value = transformer.call(value)
puts "After transform: #{value}"
end
# Configuration with Procs
logger = proc { |msg| puts "[LOG] #{msg}" }
error_handler = proc { |err| puts "[ERROR] #{err}" }
logger.call("Application started")
error_handler.call("Something went wrong")
Output:
Handler 1: hello
Handler 2: HELLO
After transform: 10
After transform: 20
After transform: 400
[LOG] Application started
[ERROR] Something went wrong