The difference between procs and lambdas in Ruby

by Jason Swett,

Note: before starting this post, I recommend reading my other posts about procs and closures for background.

Overview

What’s the difference between a proc and a lambda?

Lambdas actually are procs. Lambdas are just a special kind of proc and they behave a little bit differently from regular procs. In this post we’ll discuss the two main ways in which lambdas differ from regular procs:

  1. The return keyword behaves differently
  2. Arguments are handled differently

Let’s take a look at each one of these differences in more detail.

The behavior of “return”

In lambdas, return means “exit from this lambda”. In regular procs, return means “exit from embracing method”.

Below is an example, pulled straight from the official Ruby docs, which illustrates this difference.

def test_return
  # This is a lambda. The "return" just exits
  # from the lambda, nothing more.
  -> { return 3 }.call

  # This is a regular proc. The "return" returns
  # from the method, meaning control never reaches
  # the final "return 5" line.
  proc { return 4 }.call

  return 5
end

test_return # => 4

Argument handling

Argument matching

A proc will happily execute a call with the wrong number of arguments. A lambda requires all arguments to be present.

> p = proc { |x, y| "x is #{x} and y is #{y}" }
> p.call(1)
 => "x is 1 and y is "
> p.call(1, 2, 3)
 => "x is 1 and y is 2"
> l = lambda { |x, y| "x is #{x} and y is #{y}" }
> l.call(1)
(irb):5:in `block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError)
> l.call(1, 2, 3)
(irb):14:in `block in <main>': wrong number of arguments (given 3, expected 2) (ArgumentError)

Array deconstruction

If you call a proc with an array instead of separate arguments, the array will get deconstructed, as if the array is preceded with a splat operator.

If you call a lambda with an array instead of separate arguments, the array will be interpreted as the first argument, and an ArgumentError will be raised because the second argument is missing.

> proc { |x, y| "x is #{x} and y is #{y}" }.call([1, 2])
 => "x is 1 and y is 2"
> lambda { |x, y| "x is #{x} and y is #{y}" }.call([1, 2])
(irb):9:in `block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError)

In other words, lambdas behave exactly like Ruby methods. Regular procs don’t.

Takeaways

  • In lambdas, return means “exit from this lambda”. In regular procs, return means “exit from embracing method”.
  • A regular proc will happily execute a call with the wrong number of arguments. A lambda requires all arguments to be present.
  • Regular procs deconstruct arrays in arguments. Lambdas don’t.
  • Lambdas behave exactly like methods. Regular procs behave differently.

3 thoughts on “The difference between procs and lambdas in Ruby

  1. Guillermo Siliceo

    Thanks for this very simple and clear explanation.
    What It would help to remember this is maybe a little bit of history, why are they like this?
    Which one came first? and what are their common usages based on their different behaviors.
    I know is out of the scope of this post but I thought it could inspire you for future posts.

    Reply
      1. nick evans

        Two very different use-cases: blocks and methods.

        * blocks => procs; they’re blocks instantiated into objects,
        usually coming from `&block` in a method definition.
        * methods => lambdas; they’re anonymous functions,
        meant to behave like any other method.

        Historically, CRuby called blocks with `rb_iterate`. That specific method was deprecated and replaced with `rb_block_call` long ago, but various places still refer to yielding methods as “iterators”. That’s probably a good hint about the original intentions for block semantics.

        I could say a *lot* more, but to keep it simple, consider these:

        “`
        hash.map{|k,v|…}
        hash.map{|kv|…}

        5.times do
        no_argument_error even_though: i_ignored_the_yielded(arg)
        end

        enum.each do
        next if nope?
        break :foo if bar?
        redo if trytryagain?
        return :bar if foo?
        etc
        end
        “`

        Block-semantics mean that all of these use-cases elegantly do what you obviously want them to do. And this is why block-semantics must differ from method semantics. However method semantics are very useful too, thus lambdas.

        Reply

Leave a Reply

Your email address will not be published.