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 Handling โ€” begin/rescue/ensure/raise, and writing custom exception classes.