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
📦 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:
🔗 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:
⚡ 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:
: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
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.