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:
- The
return
keyword behaves differently - 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.
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.
Thanks!
I don’t know the full answers (yet) but I agree that those would be interesting things to know.
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.
Thank you for good explanation. Good example for ‘return issue’
Good examples and clear explanations.
Thanks)