Blocks, Procs & Lambdas

Ruby Fundamentals — Blocks, Procs & Lambdas
Ruby Fundamentals
Course 1 · Chapter 8 · Blocks, Procs & Lambdas

🎭 Blocks, Procs & Lambdas

Chapter 4 introduced blocks and 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.

⚠ return inside a Proc can crash your program

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

BlockProcLambda
Storable in a variable?NoYesYes
Created withdo...end or { } after a method callProc.new { } / proc { }lambda { } / -> ( ) { }
Argument count checkingLenientLenientStrict (raises ArgumentError)
return behaviorN/A (tied to yield)Returns from the enclosing methodReturns from the lambda itself

📐 Closing the Loop with PHP and Python

Passing "a Chunk of Code," Revisited

PHPPythonRuby
Anonymous callablefunction() {} or fn() =>lambda: expr (single expression only)Block, Proc, or Lambda — three distinct options
Multi-statement anonymous code?Yes, closures support full bodiesNo — Python lambdas are expression-onlyYes — all three (block, Proc, lambda) support full bodies
Implicit, un-named argument to every call?No — always an explicit parameterNo — always an explicit parameterYes — 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.

→ Solution

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.

→ Solution

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.

→ Solution

💎 Why This Chapter Closes the Course

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 Handlingbegin/rescue/ensure/raise, and writing custom exception classes.