Blocks, procs and lambdas
19 March 2022 | Updated on
When interviewing for my current position, the main question at the interview I stumbled over was “explain the difference between blocks and procs”. Here, I’m going to explore that, and throw in the closely related subject of lambdas for good measure.
Blocks are a fundamentally important concept in Ruby, though it’s perfectly
possible to use them all the time without realising you’re doing so. That
end? That’s a block. Anything between curly braces
(excepting hash declarations)? That’s a block. The section at the beginning
between pipes? Those are arguments given to the block.
With that context, it’s clear that when we say “block”, we mean block of code. What differentiates these blocks of code from any other arbitrary chunk of code? They can be passed to a method (or, to put it another way, sent as a message to an object).
Why is this helpful? Well, one fairly obvious way is it gives us the ability to
use the code in a loop (e.g. with
each or using
map). But there are a wide
variety of ways blocks are helpful; by giving us the ability to pass a chunk of
code around it gives us the ability to manipulate it and use it in different
So you write a method that you want to take a block. How?
Here we see the keyword you need is
yield. Also note there’s no need to
specify that the method can take a block as an argument; any method can be
passed a block (of course many will simply ignore it). A block passed in this
manner is known as an implicit block. With that
yield we can also pass
arguments to the block, as you can see.
But what if you do want to specify the block as an argument? Or what if you want a way to reference a block to pass it to another method or generally manipulate it more easily? Enter…
Procs allow us to assign a block to an object:
They also solve our above problem of being able to define a method that takes a block:
What’s this? Why is there an ampersand now? That’s the explicit block we’ve
been eagerly awaiting. The ampersand encapsulates a block that’s being passed
into a proc for us, which we can then call using the
call method. This works
the same way as
yield. Now that we have a named proc, we have the ability to
reference it and pass it onto another method if we wish.
Note that if the method you write is expecting to be passed a proc — rather than a block — there’s no need for the ampersand. The ampersand bundles a block into a proc, or unbundles in the opposite direction.
Procs are a type of closure, so called because they enclose a piece of code along with its environment. Which may sound complicated, but is fairly straightforward in practice. Let’s look at an example:
You might expect that code to print “Hello from there”, but if you run it what you get is “Hello from here” because the proc has enclosed a reference to the context in which it was defined. If the value is changed in that context, it will also change for the proc. If it changes in a different context, it won’t.
Procs achieve this encapsulation by use of the
Binding class. This isn’t the
place to examine it in detail, but it’s worth knowing that this is what allows a
proc to “reach back” to the context in which it was defined.
In Ruby, a lambda is a specific type of proc. What makes them different from a
standard proc? The way they are defined, the way they handle
return and the
way they deal with arguments. Let’s look at each of those in turn.
So you can see here we use the
-> notation, which is arguably cleaner than
What’s different about their handling of
return? If you return from a lambda,
the lambda returns execution back to where it was called from. If you return
from a standard proc, however, it tries to return from the current context. To
make that a bit clearer, if a lambda calls
return, execution is returned to
the method which called it. If a standard proc calls
return, the method that
called it will itself return.
Finally, lambdas care about the number of arguments you define them with:
As an aside, if you’re wondering why the odd-sounding name, it comes from the wider computer science concept of lambda expressions, which in turn take their name from the mathematical system of lambda calculus. This is so-called because of its use of the Greek letter lambda (λ) in its notation.
Finally, it’s worth making note of
instance_exec is a
way to call a block in the context of another block. It is by use of
instance_exec that frameworks like RSpec and FactoryBot achieve their elegant,
clean syntax. If you want to read about it, Jason Swett has a great
Blocks, procs and lambdas are a key part of Ruby and fully understanding how they work adds a powerful tool to your belt. The differences between standard procs and lambdas are subtle and could easily trip up the unsuspecting; I would suggest it’s worthwhile defaulting to the use of lambdas over procs due to their arguably more intuitive behaviour, and only switching to procs when you need their specific functionality.