Blocks, Procs & Lambdas
๐ญ Blocks, Procs & Lambdas
yield, but was upfront that a block isn't a real object โ it can't be stored in a variable or passed around explicitly, only attached to a single method call. This final chapter of Ruby Fundamentals removes that limitation: turning blocks into genuine objects (Procs), meeting their stricter cousin (Lambdas), and closing out the single feature most responsible for Ruby's reputation as an unusually expressive language.
๐ง Recap: a Block Alone Isn't an Object
[1, 2, 3].each { |n| puts n } # the { |n| puts n } part has no independent existence # This doesn't work -- a bare block isn't a value you can assign: # my_block = { |n| puts n } # SyntaxError
๐ฆ Proc โ a Block You Can Store
A Proc wraps a block of code into a real object, assignable to a variable and callable whenever you want with .call:
say_hi = Proc.new { |name| puts "Hi, #{name}!" } say_hi.call("Philip") # => Hi, Philip! say_hi.("Alex") # => Hi, Alex! -- shorthand for .call say_hi["Jordan"] # => Hi, Jordan! -- another shorthand for .call
๐ The & Operator โ Converting Between Blocks and Procs
The & operator you've glimpsed once or twice now converts in both directions: capture a method's incoming block as a named Proc parameter, or expand a stored Proc back out into a block when calling another method:
def run_twice(&block) # &block captures the incoming block as a real Proc block.call block.call end run_twice { puts "Hey!" } # Hey! / Hey! -- prints twice my_proc = Proc.new { puts "From a stored Proc" } [1, 2].each(&my_proc) # &my_proc expands the Proc back into a block for .each
โก Symbol#to_proc โ the &:method_name Shorthand
This idiom shows up constantly in real Ruby code, and it's a direct application of & converting a value into a block:
names = ["philip", "alex", "jordan"] # Long form names.map { |name| name.upcase } # Shorthand -- identical result names.map(&:upcase) # => ["PHILIP", "ALEX", "JORDAN"]
:upcase is a symbol (Chapter 2), and every symbol has a built-in to_proc method that converts it into a Proc calling that method name on whatever it's given. &:upcase then expands that Proc into a block โ three separate features from earlier chapters (symbols, blocks, &) combining into one extremely common one-liner.
๐ฏ Lambda โ a Stricter Proc
# Two equivalent ways to write a lambda add = lambda { |a, b| a + b } add = ->(a, b) { a + b } # "stabby lambda" syntax -- more common in modern Ruby add.call(2, 3) # => 5
A lambda is technically a kind of Proc, but with two behavioral differences that matter enough to treat it as its own concept:
Strict argument checking
Calling a lambda with the wrong number of arguments raises an ArgumentError, exactly like a normal method. A regular Proc is lenient โ extra arguments are silently dropped, and missing ones are filled in with nil, no error at all.
return behaves differently
return inside a lambda exits just the lambda, the same as returning from an ordinary method. return inside a plain Proc attempts to return from the enclosing method the Proc was created in โ a subtle, genuinely surprising gotcha if you don't know to expect it.
If a Proc containing return is called after the method that created it has already finished executing, Ruby raises a LocalJumpError โ there's no enclosing method left to return from. This exact failure mode is a common reason experienced Ruby developers default to lambdas over plain Procs whenever return might appear inside the block.
๐ Block vs Proc vs Lambda
The Full Picture
| Block | Proc | Lambda | |
|---|---|---|---|
| Storable in a variable? | No | Yes | Yes |
| Created with | do...end or { } after a method call | Proc.new { } / proc { } | lambda { } / -> ( ) { } |
| Argument count checking | Lenient | Lenient | Strict (raises ArgumentError) |
| return behavior | N/A (tied to yield) | Returns from the enclosing method | Returns from the lambda itself |
๐ Closing the Loop with PHP and Python
Passing "a Chunk of Code," Revisited
| PHP | Python | Ruby | |
|---|---|---|---|
| Anonymous callable | function() {} or fn() => | lambda: expr (single expression only) | Block, Proc, or Lambda โ three distinct options |
| Multi-statement anonymous code? | Yes, closures support full bodies | No โ Python lambdas are expression-only | Yes โ all three (block, Proc, lambda) support full bodies |
| Implicit, un-named argument to every call? | No โ always an explicit parameter | No โ always an explicit parameter | Yes โ the block (Chapter 4) |
Python's lambdas are deliberately limited to a single expression, which is actually the opposite trade-off from Ruby's โ Ruby's lambdas are just as full-featured as ordinary methods, and it's the plain block (not the lambda) that's the odd one out, precisely because a block isn't a regular argument at all.
๐ป Coding Challenges
Challenge 1: Capture and Call a Block
Write a method repeat_message(times, &block) that calls the given block times times, passing the current iteration number (starting at 1) into the block each time. Call it with a block that prints "Attempt #{n}".
Goal: Practice capturing an incoming block as a named Proc with & and calling it explicitly with arguments.
Challenge 2: Symbol#to_proc in Practice
Given an array of mixed-case strings, use the &:downcase shorthand with .map to lowercase every element in one line. Then do the same thing again using a full block, and compare the two lines.
Goal: Get comfortable recognizing and writing the &:method_name idiom.
Challenge 3: Proc vs Lambda Arity
Create a Proc and a Lambda that each expect exactly two arguments but simply return their sum. Call each one with only one argument. Observe (and write down in a comment) what happens differently โ one should silently produce a strange result, the other should raise an error.
Goal: Directly witness the strict-vs-lenient argument checking difference instead of just reading about it.
Blocks, Procs, and Lambdas aren't a niche feature โ they're the mechanism underneath nearly every piece of "magic" Ruby is known for, including Ruby on Rails' routing DSL, its database migrations, and RSpec's describe/it testing syntax (all bucket-listed as possible follow-ups to this course). Every one of those reads like a small custom language built on top of Ruby, and every one of them is built from exactly the same block-passing mechanics this chapter just covered.
๐ฏ What's Next
Course 1 (Ruby Fundamentals) is complete. Ruby Intermediate/Advanced (Course 2) begins with Exception Handling โ begin/rescue/ensure/raise, and writing custom exception classes.