Skip to Content
Course content

10: Blocks, Procs, and Lambdas

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was looking through some old code recently and realized I had this massive case statement that handled different types of pricing discounts. It was a nightmare to maintain. Every time the marketing team wanted a "Summer Sizzler" or a "Winter Warmup" discount, I had to open the core pricing class and add another branch. It felt wrong. In Ruby, we have a much better way to pass logic around as if it were data.

Passing behavior with blocks

Let's try to solve this. I want a method that handles the "boilerplate" of a price calculation—maybe logging the transaction or checking if the item is in stock—but I want the actual discount logic to be flexible. I'll start with a block.

def apply_discount(price)
  puts "Processing price: #{price}..."
  # I'll use yield to "jump" into the block provided
  result = yield(price)
  puts "New price: #{result}"
  result
end

# Now I can define the logic on the fly
apply_discount(100) { |p| p * 0.90 } # 10% off
apply_discount(100) { |p| p - 15 }   # $15 flat discount

This is great for one-offs. The yield keyword is the magic here; it tells Ruby, "Pause this method and go run that chunk of code between the do...end or {...}." But here's the problem: if I have a "Loyalty Member" discount that I use in ten different places in my app, I'm tired of typing { |p| p * 0.85 } over and over. I want to save that logic to a variable.

Saving logic into Procs

This is where Proc (short for procedure) comes in. A Proc is basically a block that has been "captured" into an object. Let's try to move that loyalty discount into its own variable.

loyalty_discount = Proc.new { |p| p * 0.85 }

# Wait, if I just pass it like this, it won't work:
# apply_discount(100, loyalty_discount) 
# That would throw an ArgumentError because apply_discount takes one arg, not two.

# I have to use the '&' operator to tell Ruby "treat this Proc as a block"
apply_discount(100, &loyalty_discount)

The & is a bit of Ruby syntactic sugar. It converts the Proc object into a block that yield can use. Now I can have a library of discounts—seasonal_proc, clearance_proc—and just plug them in wherever I need.

The "Return" Trap and Lambdas

Now, here is where things get weird. I noticed something frustrating while debugging a Proc. Look at this:

def calculate_with_proc(price, discount_proc)
  puts "Starting calculation..."
  discount_proc.call(price)
  puts "This line will NEVER run!"
  price
end

my_proc = Proc.new { |p| return p * 0.5 } # Note the 'return'

calculate_with_proc(100, my_proc)
# Output:
# Starting calculation...
# (The method exits immediately!)

What happened? In a Proc, the return keyword doesn't just return from the Proc; it returns from the entire method that called the Proc. It's like a grenade that blows up the whole call stack. Usually, that's not what you want. This is why lambda exists. Lambdas are "stricter" Procs.

Let's try the exact same thing with a lambda:

def calculate_with_lambda(price, discount_lambda)
  puts "Starting calculation..."
  discount_lambda.call(price)
  puts "This line DOES run!"
  price
end

my_lambda = ->(p) { return p * 0.5 } # Using the 'stabby lambda' syntax

calculate_with_lambda(100, my_lambda)
# Output:
# Starting calculation...
# This line DOES run!

The lambda is more polite. Its return only exits the lambda itself, returning control back to the method. Also, lambdas are picky about arguments. If you pass two arguments to a lambda that expects one, it'll throw an error. A Proc? It'll just ignore the extra argument and keep chugging along. Personally, I almost always use lambdas because the stricter behavior makes debugging way easier.




📋 Practical Task

Build a Dynamic Order Total Engine

You are building a checkout system where taxes and shipping fees vary by region. Instead of writing a dozen if/else statements, you will use a combination of Procs and Lambdas to calculate the final total.

Requirements:

  • Create a method called calculate_final_price that takes a base_price and two arguments: a tax_calculator and a shipping_calculator.
  • Inside calculate_final_price, use the .call method to apply the tax first, and then apply the shipping fee to that result.
  • Define a lambda for "NY State Tax" (8.875%) and a lambda for "CA State Tax" (7.25%).
  • Define a Proc for "Standard Shipping" (flat $5.00) and a Proc for "Express Shipping" (flat $15.00).
  • Execute the calculate_final_price method at least twice using different combinations of these lambdas and procs (e.g., NY Tax with Express Shipping) and print the results to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.