Procs & Lambdas

Procs & Lambdas in Ruby: how it works, when to reach for it, and what experienced engineers watch out for.

Intermediate 1 min read

Procs & Lambdas Mastery

Master Ruby's callable objects: Procs and Lambdas. Learn the differences between Proc.new, lambda, and the -> syntax, understand when to use each, and harness their power for functional programming and advanced Ruby techniques.

What are Procs and Lambdas?

Procs and Lambdas are callable objects in Ruby - they're blocks that have been converted into objects that can be stored in variables, passed as arguments, and called later.

# Blocks vs Procs vs Lambdas
# Block (not an object)
[1, 2, 3].each { |n| puts n }
# Proc (callable object)
my_proc = Proc.new { |n| puts n }
my_proc.call(42)
# Lambda (callable object)
my_lambda = lambda { |n| puts n }
my_lambda.call(42)

Key Insight: Procs and Lambdas are objects that can be stored, passed around, and called later, unlike blocks which are just syntax.

Creating Procs

There are several ways to create Procs in Ruby, each with subtle differences.

# Different ways to create Procs
# Method 1: Proc.new
proc1 = Proc.new { |x| x * 2 }
puts proc1.call(5) # => 10
# Method 2: proc (alias for Proc.new)
proc2 = proc { |x| x * 2 }
puts proc2.call(5) # => 10
# Method 3: &block conversion
def make_proc(&block)
block
end
proc3 = make_proc { |x| x * 2 }
puts proc3.call(5) # => 10

Creating Lambdas

Lambdas are a special type of Proc with stricter argument handling and return behavior.

# Different ways to create Lambdas
# Method 1: lambda keyword
lambda1 = lambda { |x| x * 2 }
puts lambda1.call(5) # => 10
# Method 2: -> syntax (stabby lambda)
lambda2 = ->(x) { x * 2 }
puts lambda2.call(5) # => 10
# Method 3: -> with multiple parameters
lambda3 = ->(x, y) { x + y }
puts lambda3.call(3, 4) # => 7

Key Differences: Procs vs Lambdas

Important: The main differences are in argument handling and return behavior.

1. Argument Handling

# Proc: flexible argument handling
my_proc = Proc.new { |x, y| puts "x: #{x}, y: #{y}" }
my_proc.call(1) # => x: 1, y: (no output for y)
my_proc.call(1, 2) # => x: 1, y: 2
my_proc.call(1, 2, 3) # => x: 1, y: 2 (ignores extra args)
# Lambda: strict argument handling
my_lambda = lambda { |x, y| puts "x: #{x}, y: #{y}" }
# my_lambda.call(1) # => ArgumentError!
my_lambda.call(1, 2) # => x: 1, y: 2
# my_lambda.call(1, 2, 3) # => ArgumentError!

2. Return Behavior

# Proc: return exits the calling method
def test_proc_return
my_proc = Proc.new { return "from proc" }
my_proc.call
"from method" # This line never executes
end
puts test_proc_return # => "from proc"
# Lambda: return exits only the lambda
def test_lambda_return
my_lambda = lambda { return "from lambda" }
my_lambda.call
"from method" # This line executes
end
puts test_lambda_return # => "from method"

Calling Procs and Lambdas

There are multiple ways to call Procs and Lambdas, each with different syntax.

# Different ways to call Procs and Lambdas
my_proc = Proc.new { |x| x * 2 }
my_lambda = lambda { |x| x * 2 }
# Method 1: call
puts my_proc.call(5) # => 10
puts my_lambda.call(5) # => 10
# Method 2: [] (bracket notation)
puts my_proc[5] # => 10
puts my_lambda[5] # => 10
# Method 3: () (parentheses)
puts my_proc.(5) # => 10
puts my_lambda.(5) # => 10

Converting Between Blocks and Procs

You can convert blocks to Procs and vice versa using the & operator.

# Converting blocks to Procs
def block_to_proc(&block)
block # Returns a Proc
end
my_proc = block_to_proc { |x| x * 2 }
puts my_proc.call(5) # => 10
# Converting Procs to blocks
my_proc = Proc.new { |x| x * 2 }
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map(&my_proc)
puts doubled # => [2, 4, 6, 8, 10]

Practical Examples

# Function composition with Procs
add_one = Proc.new { |x| x + 1 }
multiply_by_two = Proc.new { |x| x * 2 }
def compose(f, g)
Proc.new { |x| f.call(g.call(x)) }
end
composed = compose(multiply_by_two, add_one)
puts composed.call(5) # => 12 (5+1)*2
# Callback system with Lambdas
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 "Event: #{data}" }
handler.on_event { |data| puts "Logging: #{data}" }
handler.trigger_event("Hello World")

Interactive Code Runner

Practice Time: Try these Proc and Lambda examples and experiment with different callable object patterns. Understanding Procs and Lambdas will make you a more effective Ruby programmer.

Interactive Code Runner

Ruby Code Editor
Output will appear here when you run the code...

Example 1: Configuration System

# Configuration with Procs
class Config
def initialize
@settings = {}
end
def set(key, &block)
@settings[key] = block
end
def get(key)
@settings[key]&.call
end
end
config = Config.new
config.set(:database_url) { \"postgres://localhost:5432/myapp\" }
config.set(:api_key) { ENV['API_KEY'] || \"default_key\" }
puts config.get(:database_url)
puts config.get(:api_key)

Example 2: Validation System

# Validation with Lambdas
class Validator
def initialize
@validators = []
end
def add_rule(&rule)
@validators << rule
end
def valid?(value)
@validators.all? { |validator| validator.call(value) }
end
end
email_validator = Validator.new
email_validator.add_rule { |email| email.include?('@') }
email_validator.add_rule { |email| email.length > 5 }
puts email_validator.valid?(\"user@example.com\") # true
puts email_validator.valid?(\"invalid\") # false

Best Practices

1. Use Lambdas for Strict Behavior

Use lambdas when you need strict argument checking and predictable return behavior.

2. Use Procs for Flexible Behavior

Use Procs when you need flexible argument handling or want return to exit the calling method.

3. Prefer -> Syntax for Lambdas

The -> syntax is more concise and clearly indicates you're creating a lambda.

4. Use for Callbacks and Configuration

Procs and Lambdas are excellent for implementing callback systems and dynamic configuration.

Common Pitfalls

1. Confusing Return Behavior

Remember that return in a Proc exits the calling method, while return in a Lambda only exits the lambda.

2. Argument Mismatch

Lambdas will raise ArgumentError for wrong number of arguments, while Procs are more forgiving.

3. Performance Considerations

Creating many Procs/Lambdas can impact performance. Consider caching them when possible.

Summary

Procs and Lambdas are powerful tools for functional programming in Ruby:

  • Procs: Flexible argument handling, return exits calling method
  • Lambdas: Strict argument checking, return exits only the lambda
  • Use -> syntax for lambdas, Proc.new for procs
  • Perfect for callbacks, configuration, and function composition
  • Can be converted to/from blocks using the & operator

Choose the right callable object for your use case to write more expressive and flexible Ruby code.