DEV Community

Keith Bennett
Keith Bennett

Posted on • Edited on

lambdas Are Better Than procs

Many Rubyists believe that lambda and nonlambda Procs are pretty much the same and that choosing which one to use is a subjective preference. This is an unfortunate fallacy.

This article will attempt to achieve two purposes:

1) to explain the difference between lambdas and procs

2) to persuade you to use lambdas unless there is a compelling reason not to

There are many resources available that explain lambdas and procs [1], and I will assume you know at least a little about them.

Before we look at some examples, here are some characteristics of lambdas and procs:

  • a Proc instance can be either lambda or a proc [2]
  • all lambdas are Procs
  • all procs are Procs
  • code blocks behave like procs
  • you can determine the kind of Proc by calling lambda? on it

Arity (Argument Count) Checking Behavior Differences

A lambda, like a method, strictly enforces its argument count, but a proc does not. When we call a proc with the wrong number of arguments, there are no complaints by the Ruby runtime [3]:

2.6.5 :006 > pfn = proc { |arg| }
 => #<Proc:0x00007f93828bd298@(irb):6>
2.6.5 :007 > pfn.call
 => nil

In contrast, when we do the same with a lambda, we get an error [4]:

2.6.5 :002 > lfn = ->(arg) {}
    => #<Proc:0x00007f9383118ed8@(irb):2 (lambda)>
   2.6.5 :003 > lfn.call
   ...
   ArgumentError (wrong number of arguments (given 0, expected 1))

Which behavior would you prefer?

Clearly, arity checking is helpful, and we abandon it at our peril.


Return Behavior Differences

What happens when you pass a code block somewhere, and it executes a return? Does it return from the block? Well, yes, but it does much more than that; it returns from the method that yielded to the block. procs behave the same way; in addition to returning from themselves, they will return from the method in which they were called:

def using_proc
  pfn = proc { return }
  puts "Before calling"
  pfn.call
  puts "After calling"
end

# ...
2.6.5 :015 > using_proc
Before calling

Before proceeding to the lambda behavior, I'd like to point out that this proc behavior is such that implicit and explicit returns do very different things. An implicit return will return from the proc, but an explicit return will return from the context that called it! Weird, eh? Here is the same code, but without the explicit return; the proc will end and exit naturally:

def using_proc_without_return
  pfn = proc { }
  puts "Before calling"
  pfn.call
  puts "After calling"
end
# ...
2.6.5 :007 > using_proc_without_return
Before calling
After calling

When we first learn Ruby, we learn that a return at the end of a method is redundant (it is, of course), but in the case of the proc (and code block) it is not!

In contrast, a lambda's return returns from itself to the context that called it:

def using_lambda
  lfn = -> { return }
  puts "Before calling"
  lfn.call
  puts "After calling"
end
# ...
2.6.5 :008 > using_lambda
Before calling
After calling

A lambda is More Method-Like Than a proc

In both of the above cases, the lambda behaves more like a method than a proc does. The newer ->(args) notation for creating a lambda reveals that intent by defining the arguments as a method does, in a parenthesized list, and is therefore preferable to the older lambda notation:

New:

fn = ->(arg1, arg2) { ... }

Old:

fn = lambda { |arg1, arg2| ... }

Conclusion

Here are some principles I've learned to code by:

  • prefer simplicity to complexity
  • limit things to the narrowest possible scope
  • specify things with minimal ambiguity
  • use language features that minimize the risk of errors

Regarding everything said so far, the lambda wins over the proc. There is no reason to use a proc unless you specifically need the odd and potentially hazardous behaviors described above.

You may think it will never matter in your case. Maybe you're never calling the lambda yourself, but passing it to a framework such as Rails that is doing all the calling. Nevertheless, if given the added protection for free, why would you not want it? Especially since the -> notation is somewhat pictorial and more concise?


Footnotes

[1] There are many good resources; here are some that I have produced (articles and a conference talk):

[2] This terminology is unfortunate, as Proc and proc, when spoken, sound identical.

[3] In this article I've used the .call variant of calling a Proc because it is the most obvious for the reader, but in practice I prefer the shorthand notation .().

[4] The shorthand -> can be used in place of the lambda keyword to more succinctly define a lambda.


This article may be improved over time. To see its revisions you can go to its Github commit history.

Top comments (0)