08 · Blocks, Procs & Lambdas Introduction¶
Blocks are one of Ruby's defining features — a chunk of code you can pass to
a method, which the method can invoke (yield) zero or more times. Nearly
every Ruby iteration idiom (each, map, select) is built on top of them.
Blocks with do...end or { }¶
[1, 2, 3].each do |n|
puts n * 2
end
# 2 4 6
[1, 2, 3].each { |n| puts n * 2 } # same thing, single-line style
Convention: { } for short, single-line blocks; do...end for multi-line
blocks. Both are functionally identical.
yield — a method invoking its own block¶
def apply_to_five
yield(5) # pass an argument to the block
end
result = apply_to_five { |n| n * n }
puts result # 25
block_given? — making a block optional¶
def greet(name)
if block_given?
yield(name)
else
puts "Hello, #{name}!"
end
end
greet("Ada") # Hello, Ada!
greet("Ada") { |name| puts "Hey #{name}!" } # Hey Ada!
Procs — a block turned into an object¶
square = Proc.new { |n| n * n }
puts square.call(5) # 25
puts square.(5) # 25 -- shorthand for .call
puts square[5] # 25 -- another shorthand
# Passing a block explicitly with &
def apply(value, &block)
block.call(value)
end
puts apply(5, &square) # 25
Lambdas — a stricter kind of Proc¶
multiply = ->(a, b) { a * b } # "stabby lambda" syntax
puts multiply.call(3, 4) # 12
puts multiply.(3, 4) # 12
add = lambda { |a, b| a + b } # equivalent, more explicit syntax
puts add.call(2, 3) # 5
Proc vs Lambda — the two key differences¶
def proc_return
my_proc = Proc.new { return 10 } # `return` inside a Proc exits the ENCLOSING method
my_proc.call
20 # never reached if the proc runs
end
def lambda_return
my_lambda = -> { return 10 } # `return` inside a lambda exits only the lambda
my_lambda.call
20 # DOES run -- execution continues normally
end
puts proc_return # 10
puts lambda_return # 20
strict_lambda = ->(a, b) { a + b }
loose_proc = Proc.new { |a, b| [a, b] }
# strict_lambda.call(1) # ArgumentError -- lambdas check argument count strictly
puts loose_proc.call(1).inspect # [1, nil] -- procs silently fill missing args with nil
| Proc | Lambda | |
|---|---|---|
| Argument count checking | Lenient (missing args become nil) |
Strict (raises ArgumentError) |
return behavior |
Returns from the enclosing method | Returns from just the lambda |
Blocks vs Procs vs Lambdas — when to use which¶
- Block: the default for one-off iteration (
each,map) — no need to name or reuse it. - Proc: when you need to store a block as a variable or pass it around, and don't need strict argument checking.
- Lambda: when you want Proc-like reusability and method-like
behavior (strict arity,
returnscoped to itself) — the safer default when in doubt.
How It Actually Works¶
A block is not itself an object at the bytecode level — YARV compiles it
into its own private bytecode sequence, attached to the method call, that
gets invoked with a yield instruction. A Proc or lambda, on the other
hand, is a real object wrapping two things: that same bytecode plus a
captured binding — a reference to the surrounding scope's local
variables, self, and stack frame at the moment it was created. That's why a
block/proc can read and mutate a local variable from its enclosing method
even after being passed elsewhere: it isn't copying the variable, it holds
a live reference to the same binding. lambdas differ from procs in two
concrete VM-level ways: a lambda checks argument count strictly (raising
ArgumentError on mismatch, just like a method call), and return inside
a lambda unwinds only the lambda's own frame, whereas return inside a
proc attempts to unwind the enclosing method's frame — which raises
LocalJumpError if that method has already returned.
Cheat sheet¶
| Feature | Syntax |
|---|---|
| Block (multi-line) | method do \|arg\| ... end |
| Block (single-line) | method { \|arg\| ... } |
| Invoke a passed block | yield(args) |
| Check for a block | block_given? |
| Named parameter for a block | def m(&block) |
| Create a Proc | Proc.new { \|args\| ... } |
| Create a lambda | ->(args) { ... } or lambda { \|args\| ... } |
| Call a Proc/lambda | .call(args), .(args), or [args] |
🔀 See this in another language¶
Exercise¶
Write a method repeat(n) that yields to its block n times, passing the
current iteration index (0-based) each time. Then write a lambda
is_positive that checks whether a number is greater than 0, and use it with
Array#select to filter a mixed array of positive and negative numbers.