May 13, 2015
Table of contents:
Over the last couple of weeks we’ve looked at working with blocks and Procs.
A block is a chunk of code that can be passed to a method. This makes it really easy to write flexible methods that can be used in a number of different ways. If you are already familiar with other programming languages, this concept is probably already familiar to you.
Last week we looked at Procs. A Proc is basically just a block, but it is saved to a variable so you can use it like an object. This means you can make reusable procedures out of Procs that can be passed to methods. You can also use multiple Procs in a method call, whereas you can only use a single block.
Ruby also has a third similar concept to blocks and Procs known as lambdas. In today’s tutorial we’ll be looking at lambdas and how they differ from Procs.
If you already have a background in programming, you might have already come across the word lambda. A lambda is also commonly referred to as an anonymous function.
To create a lambda in Ruby, you can use the following syntax:
lambda = lambda {}
Alternatively you can use this syntax:
lambda = -> { }
However, if you create a new lambda in IRB using either of these two syntaxes, you might have noticed something a bit weird:
# => #<Proc:0x007f933a429fd0@(irb):4 (lambda)>
If you call the class
method you will see that a lambda is actually an instance of the Proc
class:
lamba.class
# => Proc
So if a lambda is also an instance of the Proc
class, what is the difference between a lambda and a regular Proc and why is there a distinction?
Well, a lambda will behave like a method, whereas a Proc will behave like a block. Let’s dig into this so we understand what’s going on under the hood.
The first difference between Procs and lambdas is how arguments are handled.
For example, we might have the following lambda and Proc that do exactly the same thing, in this case, accept a name
and puts
a string to the screen:
lambda = ->(name) { puts "Hello #{name}" }
proc = Proc.new { |name| puts "Hello #{name}" }
We can call each of these by using the call
method and passing a name as the argument:
lambda.call('Philip')
# => Hello Philip
proc.call('Philip')
# => Hello Philip
All good so far, both the lambda and the Proc behave in exactly the same way.
However, what happens if me don’t pass an argument?
lambda.call
# => ArgumentError: wrong number of arguments (0 for 1)
proc.call
# => Hello
When a lambda expects an argument, you need to pass those arguments or an Exception will be thrown. However, in the case of the Proc, if the argument is not passed it automatically defaults to nil
.
This is because a lambda will act like a method and expect you to pass each of the defined arguments, whereas a Proc will act like a block and will not require strict argument checking.
A second difference between a lambda and a Proc is how the return
statement is handled.
To illustrate this, lets take a look at a code example:
def lambda_method
-> { return 'I was called from inside the lambda' }.call
return 'I was called from after the lambda'
end
Here we have a method that contains a lambda and an return
statement. When the lambda is called it will return a string of text to the method.
When we call this method and puts
the return value to the screen, what would you expect to see?
puts lambda_method
# => "I was called from after the lambda"
So when the method is called, the lambda is called from inside the method, then the return statement returns the string of text after the lambda.
However, imagine we also had a proc version of this method:
def proc_method
Proc.new { return 'I was called from inside the proc' }.call
return 'I was called from after the proc'
end
This is basically the same method but instead of using a lambda we are using a Proc.
Now if we run this method, what would you expect to see?
puts proc_method
# => "I was called from inside the proc"
When a lambda encounters a return
statement it will return execution to the enclosing method.
However, when a Proc encounters a return
statement it will jump out of itself, as well as the enclosing method.
To further illustrate this behaviour, take a look at this example:
-> { return }
# => #<Proc:0x007f9d1182d100@(irb):2 (lambda)>
When you create a lambda in irb and use a return
statement everything is fine.
However if you try to do the same thing with a Proc, you will get an Exception:
Proc.new { return }.call
# LocalJumpError: unexpected return
This is basically the same as what we saw whilst wrapping the lambda and the Proc in a method, however in this case, the Proc has nothing to jump back to.
Blocks, Procs and Lambdas are all pretty similar. Each has their own characters, place and purpose within the Ruby language.
It is important to understand the characteristics of things like blocks, Procs and lambdas because it will make it a lot easier to understand other people’s code.
When you learn a new idea it often feels tempting to jump right in and start using it all the time. This usually leads you to using the new technique in the wrong situations.
Don’t worry about using new ideas straightaway. Instead, start reading other people’s code to see how they have implemented the same idea.
Once you can understand and recognise how and why another developer has written a certain piece of code, you will be much better equipped to make your own design decisions.