~tsvallender

My name is Trevor, I’m a software engineer specialising in Ruby on Rails. I’m also a dad, geek and tabletop gamer.

Profile photo of Trevor Vallender

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

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 section between do and 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 contexts.

Calling blocks

So you write a method that you want to take a block. How?

def run_a_block
  yield 'hello'
  yield 'world'
end

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

Procs allow us to assign a block to an object:

awesome_proc = Proc.new { |x| puts x}

They also solve our above problem of being able to define a method that takes a block:

def run_a_block &my_proc
  my_proc.call 'hello'
  my_proc.call 'world'
end

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.

Closures

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:

greeting = "Hello from here"
printer = Proc.new { puts greeting }

def call_proc my_proc
  greeting = "Hello from there"
  my_proc.call
end

call_proc printer

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.

Lambdas

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.

awesome_lambda = -> { puts "I’m an awesome lambda" }
awesome_lambda.call

# Or, with arguments:
awesome_lambda = -> (name) { puts "Hello #{name}" }

So you can see here we use the -> notation, which is arguably cleaner than Proc.new.

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:

awesome_lambda = -> (x, y) { puts x, y }
awesome_proc = Proc.new { |x, y| puts x, y }

# These are all fine:
awesome_lambda(1, 2)
awesome_proc(1, 2)
awesome_proc(1)

# But this will cause an exception due to the incorrect number of arguments:
awesome_lambda(1)

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.

instance_exec

Finally, it’s worth making note of instance_exec here. 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 post.

Conclusion

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.