Ruby Blocks, Procs, and Lambdas

Blocks are chunks of code passed to methods. They are one of Ruby's most powerful features — you'll see them everywhere in Rails and standard libraries.

Blocks with yield

Ruby
def timer
  start = Time.now
  yield            # runs the block passed by the caller
  elapsed = Time.now - start
  puts "Done in #{elapsed.round(2)}s"
end

timer { sleep(1) }  # "Done in 1.0s"

Block Arguments

Ruby
def each_with_label(items)
  items.each_with_index do |item, i|
    yield i, item
  end
end

each_with_label(["apple", "banana", "cherry"]) do |index, fruit|
  puts "#{index + 1}. #{fruit}"
end
# 1. apple
# 2. banana
# 3. cherry

do/end vs {}

Convention: use do...end for multi-line blocks and { } for single-line:

Ruby
# Single line
[1, 2, 3].map { |n| n * 2 }

# Multi-line
[1, 2, 3].map do |n|
  doubled = n * 2
  puts doubled
  doubled
end

Procs

A Proc is an object that wraps a block:

Ruby
square = Proc.new { |n| n * n }
square.call(5)   # 25
square.call(10)  # 100

# Shorthand
triple = ->(n) { n * 3 }
triple.call(4)   # 12

Lambdas vs Procs

Ruby
greet = lambda { |name| "Hello, #{name}!" }
greet = ->(name) { "Hello, #{name}!" }  # same thing

# Key difference: arity checking
my_proc = Proc.new { |a, b| [a, b] }
my_proc.call(1)     # [1, nil] — no error, extra args ignored

my_lambda = ->(a, b) { [a, b] }
my_lambda.call(1)   # ArgumentError — wrong number of arguments

Closures

Procs and lambdas capture their surrounding scope:

Ruby
def make_counter
  count = 0
  incrementer = -> { count += 1 }
  resetter = -> { count = 0 }
  [incrementer, resetter]
end

inc, reset = make_counter
inc.call    # 1
inc.call    # 2
reset.call  # 0
inc.call    # 1

Useful Higher-Order Methods

Ruby
nums = [1, 2, 3, 4, 5]

nums.map { |n| n * 2 }         # [2, 4, 6, 8, 10]
nums.select { |n| n.even? }    # [2, 4]
nums.reject { |n| n.even? }    # [1, 3, 5]
nums.find { |n| n > 3 }        # 4
nums.any? { |n| n > 4 }        # true
nums.all? { |n| n > 0 }        # true
nums.none? { |n| n < 0 }       # true
nums.reduce(0) { |sum, n| sum + n }  # 15

Tips

  • Blocks are not objects — Procs and lambdas are.
  • Use yield to call a block from within a method.
  • Prefer lambda over Proc.new for strict argument checking.

Next: testing.