Ruby refresher: proc - lambda - block

A hand holding a transparent ball filled with red smoke
We're all Nevilles at times!

This is one of those posts that might make you think “Again?” when you read a title of it. Still, every now and then the question pops up — “What’s the difference between proc and lambda?” and then I can’t really remember details. That’s why I’m writing this post — so that I can remember the answer at last. Secondly, chances are it will serve as a refresher for all those out there who can’t remember it either!

proc vs lambda

Both are closures. Both respond to the call method. Both are instances of the Proc class. You can use them as anonymous functions, yet…

Scope

Let’s start with lambda.

The outcome is pretty predictable. We call the lambda, the "Hey there!" is assigned to result and our console prints $ Let’s print this! Result: Hey there!.

What about proc?

Wait a second! The console prints $ Hey there! right away!

So what happened?

Contrary to lambdas, procs don’t have their own scope to return from. That’s why they’re executed as if they’re code was cut out of their bodies and pasted in the place where they were called. In our example, proc’s return is executed as if it was return_from_proc’s own return. As a result, the execution never gets to the Let's print this! (...) line!

Argument checking

Take a look at the example below. Both lambdas and procs accept two arguments (a and b) and return an array of them.

Just like before, the results are quite predictable.

$ (1, 2):    [1, 2]
$ medium.rb:1:in `block in <main>': wrong number of arguments (given 3, expected 2) (ArgumentError)
 from medium.rb:3:in `<main>'

Obviously, If I comment out the second call, the program will throw a similar exception at the third call. Just like a method.

It’s not the case for procs, though.

Let’s see what our console tells us now…

$ (1, 2):    [1, 2]
$ (1, 2, 3): [1, 2]
$ (1):       [1, nil]

Whoa! procs don’t check the number of arguments.  You passed too many? Cool, it will take only the number it needs. Too few? No problem, it will use nils.

Alternative syntaxes

These are some alternative syntaxes you can come across

  • Proc.new { |name| "I'm, #{name}!" } equals  proc { |name| "I'm, #{name}!" }
  • lambda { |name| "hey, #{name}!" } equals  ->(name) { "hey, #{name}!" }

Verdict

In my humble opinion, the difference in scopes is the most important reason to use lambdas over procs. If you ever find yourself thinking of using this trick proc offers, please don’t. I’m also not a fan of the trick with argument checking. The very fact they’re called tricks in the official docs actually speaks for itself.

Blocks

Basics

Intuitively, block is nothing more than a block of code. What’s interesting though, is the way it works with Ruby’s closures (methods, procs, lambdas).

To illustrate this, let’s write a function we could use in a similar way to the map function.

What you can see in lines 3 and 8–10 are blocks— lines of code wrapped in do end or curly braces. You can pass a block to any closure although the closure won’t always use the block. As you can see, Enumerable#map (line 3) and our MyMap#call closures do use blocks.

Let’s focus on MyMap#call then. The block’s argument is element and it returns an increment of it. How do we pass an argument to the block though? Line 3 — yield keyword does exactly that. It takes an item and passes it to the block as element, then returns the block’s result. That’s why if you print the result you’ll get:

$ [2, 3, 4]

Catch a block!

If you think about it, blocks are quite similar to procs. Both blocks and procs are essentially procedures that accept arguments. So what if we want to somehow… store a block as an object? Operator & to the rescue!

It turns out all closures accept an additional implicit argument — a block! If we write the argument implicitly with & in the front of it, we get access to a proc that holds the block’s content. Then the & operator transforms the block into a proc!

Build a block?

Actually, & works both ways. I bet you saw a line like this many, many times:

Article.all.map(&:author)

& performs an inverse transformation here — it takes a closure or its symbol and transforms it into a block which is what map accepts.

Knowing that, we can refactor our example:

First, let’s read the MyMap#call method. It takes items and, as with any closure, a block. It captures the block as a proc using & operator. Then, it transforms it back to a block so it can be passed to the map. increment is a lambda that accepts an element and returns it’s increment.

Then we just need to transform this lambda to a block using & operator and pass it to our call method.

Last words

I hope you liked this quick Ruby refresher. These kind of Ruby nuances might be a bit confusing but hopefully you found them also exciting. My advice is not to get too excited and start overusing these things.

lambdas are cool but extracting a block like I did in the last example is usually overkill. If it‘s big, it is often better to simply extract parts of it into other methods/classes.

All in all, I believe every Ruby developer should be aware of the differences between blocks, procs and lambdas. It might allow you to avoid some hard-to-catch bug. Other time it’ll help you read some gem’s insides. And who knows, maybe it will lead to a really nice designed, robust code?

Share this on: