In the last post, we delved into the world of blocks. We explored what a block is, the different ways that a block can be invoked and also touched upon their limitations. Check out the post here.

In the final instalment in the series, we’ll explore procs and lambdas.

What’s are procs and lambdas?

Blocks seem great and all but what about if you want to re-use them or pass them to another method? That’s where procs and lambdas come in…

It might be helpful to think of a proc as a saved block with a few extra nifty features whilst lambdas can be considered as a fully fledged anonymous functions.

Defining a proc

Unlike blocks, procs are objects. As instances of the Proc class, they can be instantiated and assigned to a variable as follows:

    my_proc = Proc.new { puts "This is my proc" }

    # or

    my_proc_two = Proc.new do |name|
     puts "Hi, I'm #{name}"
    end

Got it! And what about lambdas?

Lambdas are procs 2.0 and are the most method-like when compared to blocks and procs. We’ll look at some of the reasons why later on in this post. They too are instances of the Proc class and can be defined in the following way:

    my_lambda = lambda do |name|
    	puts "Hi #{name}" }
    end

    # or

    my_lambda_two = -> (name) { puts "Hi #{name}" }

Calling a proc or lambda

We use the #call method to invoke procs and lambdas:

my_lambda = lambda { |name| puts "Hi there #{name} " }

my_lambda.call("Serena")

So, what are the differences between them?

1. Handling arguments

Like blocks, procs and lambdas can handle arguments:

    my_proc = Proc.new { |name| puts "Proc: Hi #{name}" }

    my_lambda = lambda { |name| puts "Lambda: Hi #{name}" }

There’s a crucial difference between how procs and lambdas handle missing or extra arguments though. A lambda will strictly enforce its argument count whereas a proc will not. Let’s take a look at a few examples.

If a proc or lambda is defined with parameters but no arguments are actually passed to it, procs will continue executing normally whereas lambdas will raise an ArgumentError just like a standard Ruby method would:

    my_proc = Proc.new { |name| puts "Proc: Hi #{name}" }
    my_lambda = lambda { |name| puts "Lambda: Hi #{name}" }

    my_proc.call
    => "Proc: Hi"

    my_lambda.call
    => proc.rb:xx:in `block in <main>': wrong number of arguments (given 0, expected 1) (ArgumentError)

Similarly, if an extra argument is supplied when a proc or lambda is being called, procs will continue executing normally whilst lambdas will continue to raise an ArgumentError:

    my_proc.call("Serena", "Berlin")
    => "Proc: Hi"

    my_lambda.call("Serena", "Berlin")
    proc.rb:xx:in `block in <main>': wrong number of arguments (given 2, expected 1) (ArgumentError)

Since procs are much freer when it comes to how they handle arguments, it is often preferable to use lambdas over procs to minimise the risk of such errors.

2. Handling returns

Another distinguishing feature of lambdas as opposed to procs is the way in which they handle return statements.

When a proc contains an explicit return statement and is used inside a method, the proc will return from the entire method in which it was called. In the example below, this means that the last line in the method will never be executed:

    def greeting_with_proc
      proc = Proc.new { return 'Serena' }
      name = proc.call

      "Hello #{name}"
    end

    puts greeting_with_proc
    => "Serena"

Calling return inside a lambda however, will only return from the lambda itself and not the context that calls it. This means that the final line in the method will be executed.

def greeting_with_lambda
  lambda = -> { return 'Serena' }
  name = lambda.call

  "Hello #{name}"
end

puts greeting_with_lambda
=> "Hello Serena"

Key takeaways

Coming soon