Unless you understood the “closure” concept from other languages, it might have been confusing to you initially in Ruby. “Closure”, is anachronistic, except to Ruby (and some functional programming languages).
Here’s a Merriam-Webster definition of a closure:
It’s the same here: A Ruby closure “encloses a bunch of code to be executed”. It’s an “enclosure”. According to Wikipedia, a closure is a technique for implementing lexically scoped name binding in a language with first-class functions.
Consider this snippet:
2.times do
topic = 'Ruby closures'
puts "An adventure with #{topic}."
end
#=> An adventure with Ruby closures.
#=> An adventure with Ruby closures.
#=> 2
We have “enclosed” topic
and puts
between do
and end
. Anything outside of that region can’t have access to topic, but the puts
method can because it’s inside the same environment (lexically scoped) as the variable assignment (name binding). It’s this technique of combining methods and variables we call closure. Note: { ... }
and do ... end
are equivalent when passed to a method. I’ll be using them interchangeably.
How about this piece of code?
lam = lambda do |topic|
puts "An adventure with #{topic}."
end
lam.call('Ruby closures')
#=> An adventure with Ruby closures
If you look closely, it’s doing a similar thing as the snippet before. Now, how about the last one?
prok = Proc.new do |topic|
puts "An adventure with #{topic}."
end
prok.call('Ruby closures')
#=> An adventure with Ruby closures.
These forms represent ways to enclose code in Ruby. We’ve seen a block, lambda and a Proc represented respectively as: {}
, lambda {}
and Proc.new {}
in the previous code snippets. These are closures in Ruby. In all three, we have set a local variable topic
and passed it to a local method puts
. You might be wondering where we set the topic
variable in lam
and prok
. We didn’t explicitly set it as topic = "Ruby closures"
as we did in the very first code snippet. The reason is; the #call
method for the Proc
and lambda
does this for us. If you look at the documentation for Proc#call
, we have:
call(params, …) → obj Invokes the block, setting the block’s parameters to the values in params.
One subtle difference worth noting about Ruby blocks (as closures) is, in Ruby, blocks can’t be represented as objects, they just provide a simple way to group code but you can’t bind them to variables. By themselves, they are quite useless. Procs and Lambdas, on the other hand, are, what some call first-class citizens.
Blocks.
I stand to be corrected, but as I mentioned earlier, blocks by themselves don’t mean anything. They are usually passed as arguments to methods, but they do also help us group our code to modify its behaviour. Here’s an example:
(1..10).each { |v| puts v if v.even? }
# Output: #
# 2
# 4
# 6
# 8
# 10
We can see here that we’re iterating a Range and printing out all even numbers. Now, what if we want to print out all numbers that are even and prime at the same time? Here’s when blocks showcase their usefulness. We only have to modify the code in the block to do this to get:
require 'prime'
(1..20).each { |v| puts v if v.even? && v.prime? }
# => 2
We just changed the behaviour of our code by grouping what’s in our block. We had to require the Prime
standard library to make use of the prime?
method. We could have also implemented our method for picking primes if we wanted. We’ve seen how we can use Ruby blocks to group our code to enumerate. But in this post, we’re talking about closures. Have you already seen how the block above acts as closure by assigning each number in the range of 1 to 20 to the variable v
? If not how about this bit of code here:
def shout(word, position)
position.times { |x| puts "#{ x }: #{ word }"}
end
shout("SHOUTING!", 2)
# Output: #
# 0: SHOUTING!
# 1: SHOUTING!
In this one, we’re assigning the word
variable to the string SHOUTING!
while assigning the second parameter, an Integer, to the variable position
, and using x
to represent each number from 0 up to position
for the number of times we want the string printed. Keep the definition of closure in mind. Does the last code satisfy it?
Now you know a bit about blocks, but in the examples shown so far, we’re only using Ruby blocks to encapsulate behaviour and passing them to Ruby’s methods. We can also write our methods that accept blocks.
In Ruby, we are blessed with the yield
and block_given?
methods. These methods help us create our methods that accept blocks too. Anytime you see yield
in Ruby, translate it as “Hey, do whatever the passed block says”. block_given?
is pretty intuitive. This method simply returns a boolean of whether or not there was a block passed. Here’s a simple example to get the concept across:
def shout
yield
end
# Call the #shout method with a block.
shout { puts "SHOUTING!" }
# => SHOUTING!
Try the method above and call yield
twice or three times and see what happens. Does this make any sense? If you didn’t pass a block to this method when you called it, you’d be seeing
no block given (yield) (LocalJumpError)
You can try it. And that’s where the block_given?
comes in handy. Here’s how you can make use of the block_given?
method:
def shout
block_given? ? yield : puts("Sir, you didn't pass any block.")
end
# Call the #shout method without a block.
shout
# => Sir, you didn't pass any block.
That’s pretty much all there is to it! But you might be wondering what the practical use of all of this is: Pretty Object Initialisation! Like this:
module Genus
class Species
attr_accessor :name, :height, :weight, :ethnicity
def initialize
yield self if block_given?
end
end
end
human = Genus::Species.new do |characteristics|
characteristics.name = "Emmanuel Hayford"
characteristics.height = 188 # cm
characteristics.weight = 104 # kg
characteristics.ethnicity = "black"
end
p human.ethnicity
# => "black"
We are setting self
(which is an instantiated object of the Species class) to the characteristics
variable.
Procs.
Even though everything in Ruby is an object, constructs such as blocks and methods are not. However, they can we can turn them into objects with Procs. So mostly Procs are blocks represented as objects that you can call methods on or allocate to memory.
Let’s refactor our very first example to use a Proc.
So we’ll have this bit of code:
2.times do
topic = 'Ruby closures'
puts "An adventure with #{topic}."
end
changed to this:
prok = Proc.new {
topic = 'Ruby closures'
puts "An adventure with #{topic}."
}
# `Proc.new` is equivalent to `proc`
2.times do
prok.call
end
# => An adventure with Ruby closures.
# => An adventure with Ruby closures.
Because proc
is now an object, you can invoke the call
method on it to return the block. We could refactor further to make this more dynamic by making the block or Proc object take an argument:
prok = Proc.new { |topic|
puts "An adventure with #{topic}."
}
2.times do
prok.call('Ruby closures')
end
# => An adventure with Ruby closures.
# => An adventure with Ruby closures.
Here’s something interesting; for a Proc object that takes a single argument, for example, you can call it in a couple of different ways. Ruby allows us to omit the call to call
. So line 6 of the code above is equivalent to the following:
prok.('Ruby closures')
and prok === 'Ruby closures'
.
If however, you wanted to work with multiple arguments, you could pass your arguments to the Proc object as elements of an array. So let’s assume this is our code:
prok = Proc.new { |x, y| puts x + y }
All methods below for calling prok
are valid:
prok.call [7,3]
prok === [3, 7]
prok[6, 4]
# => 10
# => 10
# => 10
All the above are valid for lambda
s.
Lambdas.
Lambdas are Proc objects that respect arity and treat the return
keyword a bit differently.
Here’s the behaviour worth noting about Procs and Lambdas. Consider the following:
cool_lambda = lambda { |first_name, nationality| puts "#{first_name} is #{nationality}."}
cool_proc = proc { |first_name, nationality| puts "#{first_name} is #{nationality}."}
cool_lambda.call("Matz", "Japanese")
cool_proc.call("Aaron", "American")
Everything works as expected. cool_lambda
’s signature accepts two arguments and it’s strict about that, if you passed one more or one less argument, it’d blow up with something like:
wrong number of arguments (given 3, expected 2) (ArgumentError)
cool_proc
on the other hand will just do what the block specifies and silently ignore extra params or their absence.
Here’s the case about the return
keyword with closures:
class ClosureExperiment
def call_closure(closure)
puts "Calling #{ check_closure_type(closure) }."
closure.call
puts "#{ check_closure_type(closure) } got called!"
end
def check_closure_type(unknown_closure)
unknown_closure.lambda? ? "Lambda" : "Proc"
end
end
closure = ClosureExperiment.new.call_closure(lambda { return })
# => Calling Lambda.
# => Lambda got called!
closure = ClosureExperiment.new.call_closure(proc { return })
# => Calling Proc.
From the above, it can be observed that while Procs will snap out immediately on seeing return
, Lambda’s hesitate until everything in their context is executed.
One more difference between these two closures is when a Lambda is defined with a default argument and passed an array, it always returns the default argument passed to it while Proc ignores it unless there are no extra arguments, then the default argument is implemented as in the following:
lam = lambda { |a, b = :foo| p a, b }.call([1,3,5])
# => [[1, 3, 5], :foo]
prok = proc { |a, b = :foo| p a, b }.call([1,2,3])
# => [1, 2]
prok = proc { |a, b = :foo| p a, b }.call([1])
# => [1, :foo]
That’d be it for how Procs and Lambdas differ.
To conclude, I like to think of Procs as blocks represented as objects while Lambdas are a variant of Procs that raise errors when you pass in the wrong number of arguments.