Ruby Closures 101

Learn about blocks, procs and lambda objects

Guilherme Pejon
The Startup
8 min readOct 11, 2019

--

In programming languages with first-class functions, functions can be stored in variables, passed as arguments to other functions and even sent as return arguments. First-class functions are a necessity for the functional programming style.

A closure is a first-class function with an environment. The environment is a mapping associating each variable of the function with the value to which the name was bound when the closure was created.

Note: We'll dive deeper into closures and see it in practice at the end of this article.

Ruby doesn’t have first-class functions, but it does have closures in the form of blocks, procs and lambdas. Blocks are used for passing blocks of code to methods, and procs and lambda’s allow storing blocks of code in variables.

Sounds complicated? Don't worry, we'll go over a few examples and it will all make sense in a few minutes.

Blocks

A ruby block.

The simplest possible definition of a block is that it is a snippet of code created to be executed later. It presents itself enclosed between braces {} or the keywords do and end. If you ever used the methods #each or #map before, then congratulations, you already know how to use blocks!

You can also think of a block like the body of a method. Like a method, it can take multiple parameters, which are defined between two pipes characters, like this: |argument1, argument2, ...|.

Let’s go ahead and dive into an example to make things clearer. Let’s count to three with the help of blocks!

# Form 1: multi-line blocks
[1,2,3].each do |n|
puts n
end
# Form 2: single line blocks
[1,2,3].each { |n| puts n }
# both of these blocks will return the following:
# 1
# 2
# 3

As you can see, in both forms, n is the parameter sent to the block body, which in our case, prints it to the standard output using puts.

Explicit block calls

If you want to pass a block to your method, you have two choices: the explicit or implicit method. Let's start with the explicit method. Even though the syntax might be a little bit weird, the concept is simple. Let's go ahead and see an example and I will explain it afterwards.

def my_method(&block)
block.call
end
my_method { puts 'Hello World!' }# Hello World!

What we are doing here is sending the { puts 'Hello World!' } block as an explicit argument to our method. Then, inside our method, we can execute this method by calling block.call.

Now, there are three things to notice here.

1) If you call this method without passing it a block, you will get an ArgumentError, like so:

def my_method(&block)
block.call
end
my_method('Hello World!')# ArgumentError: wrong number of arguments (given 1, expected 0)

Notice that even though we are explicitly saying that our method takes a block as an argument, the amount of arguments required by that method remains as zero, which means that it is optional.

2) &block is only a convention, you can use any argument name to store your block inside your method.

def my_method(&my_sweet_block_name)
my_sweet_block_name.call
end
my_method { puts 'Hello World!' }# Hello World!

3) You can execute your block as many times as you want inside your method, you just need to call it again.

def my_method(&block)
block.call
block.call
block.call
end
my_method { puts 'Hello World!' }# Hello World!
# Hello World!
# Hello World!

Implicit block calls

Implicit block passing works by calling the yield keyword inside your method. The yield keyword is a special word that finds and calls a passed block, so you don’t have to add the block to the list of arguments with &block, or explicitly call it with block.call.

def my_method
yield
end
my_method { puts 'Hello World!' }# Hello World!

You can use this method for when you don't need to store the block in a variable, and you just want to run it at some point inside your method.

Note: You can also call your block many times with multiple yield calls

You may have seen this type of block call inside the view files of your Rails projects, as a combination of the content_for and yield methods, and now you understand exactly what they do.

Using block params

Just like a method, you can send parameters to your block calls. Here's how to do it, on a self-explanatory example, using both of the methods described above. Explicit version:

def explicit_version(&block)
block.call(1)
block.call(2)
block.call(3)
end
explicit_version { |number| puts number * 2 }
# 2
# 4
# 6

Implicit version:

def implicit_version
yield 1
yield 2
yield 3
end
implicit_version { |number| puts number * 2 }
# 2
# 4
# 6

Procs (short for procedure)

A "Proc" is basically a block that can be directly stored in a variable. To create a proc, you call Proc.new and pass it a block. To run your block you need to use the call method, just like we did before. Here's an example:

proc = Proc.new { puts "Hello World!" }
proc.call
# Hello World!

One advantage of using proc is that you can send multiple blocks as arguments to a method, just like normal parameters. Like this:

def my_method(print_hello, print_number)
print_hello.call
print_number.call(2)
end
print_hello = Proc.new { puts "Hello World!" }
print_number = Proc.new { |n| puts n }
my_method(print_hello, print_number)
# Hello World
# 2

:to_proc

Hashes, symbols and methods can be converted to procs using their #to_proc methods. Here's what I mean:

{ a: 1, b: 2 }.to_proc
#<Proc:0x00007fe63292f4a8>
:a.to_proc
#<Proc:0x00007fe63296e360(&:a)>
def my_method
puts 'Hello World!'
end.to_proc
#<Proc:0x00007fe63293e4a8(&:my_method)>

This means that they can all be stored into variables, passed around as arguments, and even returned from methods.

You probably used the to_proc method implicitly before, combined with map, like so:

[1,2,3].map(&:to_s)
# ["1", "2", "3"]

What we are basically doing here is sending a block to our map method using the explicit notation (with an ampersand). In this case, our block is the :to_s symbol, which gets automatically converted into a proc with its #to_proc method. After the conversion, the block is called with the call method, like we've seen before.

So here's our last snippet of code, after to_proc was called on it, to make things clearer:

[1,2,3].map { |i| i.to_s }
# ["1", "2", "3"]

Note: If you're curious about how to_proc works under the hood, you can take a look at its source code, it's pretty simple.

Lambdas

A lambda is basically a special type of proc. Let's take a look on how we can declare one.

say_something = -> { puts 'This is a lambda' }
say_something.call
# This is a lambda

So far it's pretty much the same as a proc object, apart from the syntax, but a lambda object is a lot more similar to a regular method than a proc is. Let's take a look at a few key differences.

1) Just like a regular method, a lambda object will throw an error if you call it with the wrong number of arguments, but a proc object won't.

print_n = lambda { |n| puts "You will not see this message" }
print_n.call
# ArgumentError: wrong number of arguments (given 0, expected 1)
print_n = Proc.new { |n| puts "You will see this message" }
print_n.call
# You will see this message

2) A lambda object will return normally, like a method would, but a proc will return from its current context. Here's an example, starting with the proc behavior:

def call_proc
puts "This will be printed"
my_proc = Proc.new { return 1 }
my_proc.call
puts "This will not be printed"
end
call_proc
# This will be printed
# 1

And here's what happens when a lambda returns:

def call_lambda 
puts "This will be printed"
my_proc = -> { return 1 }
my_proc.call
puts "This will also be printed"
end
call_lambda
# This will be printed
# This will also be printed

So here's a summary of the key differences between lambdas and procs:

  • Lambdas are defined with -> {} or lambda {} and procs with Proc.new {};
  • Procs return from the current context, while lambdas return just like a method would;
  • Procs don’t care about the correct number of arguments, while lambdas will raise an exception if called with the wrong number of arguments.

Closures

Ruby procs and lambdas have one more special attribute. When defined, they will carry the current scope with them, like local variables and methods from the context in which they were created.

That doesn't mean they will carry the variable values with them, but actually a reference — or pointer, for C lovers. So if the variable value changes after the proc was defined, they will always have the latest version with them.

Let's go ahead and see an example to try to make some sense out of this:

def my_method(my_proc)
count = 3
my_proc.call
end
count = 1
my_proc = Proc.new { puts count }
count = 2
puts my_method(my_proc)
# 2

It may seem a little counter-intuitive, but if you look closely you will see that it makes sense. Even though the proc was declared when count was 1, since it has a reference to count, it was automatically updated when we wrote counter = 2. Also, the proc ignored the count = 3 line because since it is a closure, it already has its variables stored.

Note: This is a great question for a Ruby job interview, don’t you think?

Summary

Now that we understand all the ins and outs of both blocks, procs and lambdas, let’s take a step back and summarize their main differences.

  • Blocks are used extensively in Ruby for passing snippets of code to methods. By using the yield keyword, a block can be implicitly passed without having to be explicitly converted into a proc.
  • When using parameters prefixed with ampersands, passing a block to a method results in a proc in the method’s context. Procs behave like blocks, with the advantage of being able to be stored into variables.
  • Lambdas are procs that behave like regular methods, meaning they enforce arity and return as methods instead of in their parent scope, like procs do.

And that's it for this article! Hope you enjoyed it and that it wasn't too confusing. If you have any questions, feel free to ask me! I'm planning on writing another article as a follow-up to this one, with real life examples of closures in Ruby, so stay tuned for that!

Also, if you are a rails developer, here is some of my other articles that you might be interested in:

--

--

Guilherme Pejon
The Startup

Passionate about web development, open source projects and coding puzzles.