lambda and Proc - any difference?
- 18 November 2016
- Ruby
- lambda and Proc - any difference?
Hey! In previous article we discussed blocks and today I'm going to describe lambdas and Procs. We will understand similarities and difference between them.
lambda
Generally speaking there is nothing hard in Procs and lambdas. Lambda is just a function without a name. It's so called anonymous function.
As we know - almost everything in Ruby is an object. So lambda is an object as well. It's an object which has method call
which calls anonymous function. Example:
hello = lambda { puts "Hello" }
hello.call # => Hello
Looks simple, right? We "saved" block of code into hello
variable and called it by using hello.call
when we needed. We can pass params to lambda:
hello = lambda { |word| puts word }
hello.call("Hello") # => Hello
If we call that lambda without param, Ruby will throw an error:
hello = lambda { |word| puts word }
hello.call("Hello") # => Hello
hello.call # => wrong number of arguments (0 for 1) (ArgumentError)
We see that lambda behaves as a regular method and if we don't pass all required arguments it will raise an error ArgumentError: wrong number of arguments
.
Lambda behaves as a regular method inside other methods as well. Check the following example:
def say_hi
l = lambda { "Hi from lambda" }
puts "Lambda will say hi:"
puts l.call
puts "It worked!"
end
say_hi
# Lambda will say hi:
# Hi from lambda
# It worked!
As we can see lambda's call didn't interrupt execution of say_hi
function. Lambda just returned its value and code of say_hi
continued to execute.
Another important thing - lambda keeps all scope where it was declared. In our example that means that all variables that was declared before lambda (inside method say_hi
) will be available inside lambda:
def say_hi
x = 10
l = lambda { "I see x = #{x}" }
l.call
end
puts say_hi # => I see x = 10
Because lambda has an access to scope's variables - it can change them as well. But variables which declared inside lambda - gets destroyed right after execution and don't leak into global scope.
def count
counter = 1
l = lambda do
counter += 1
lambda_var = 100
end
l.call
puts counter # => 2
puts defined?(lambda_var) # => nil
end
As we see lambda's call changed counter
variable, but variable lambda_var
lives only inside lambda and not accessible from outer scope. One thing to remember that Procs have the same approach of work with scoping.
We can define lambda using keyword lambda
or using short version ->
:
# without params
l = -> { puts "Hello" }
l = lambda { puts "Hello" }
# with params
l = -> (word) { puts word }
l = lambda { |word| puts word }
Let's go to interesting part. Let's check lambda's class:
puts lambda { |word| puts word }.class # => Proc
Yes, lambda
it's just a special case of Proc
. So, now we can move forward and learn something new about Procs.
Proc
Proc
is a short name of procedure
and yes, it's a block of code which we can assign to variable and call when we need it.
# without param
p = Proc.new { puts "Hello" }
p.call # => Hello
# with param
p = Proc.new { |word| puts word }
p.call("Hello") # => Hello
There is also a short version of Proc.new
- just proc
:
# without param
p = proc { puts "Hello" }
p.call # => Hello
# with param
p = proc { |word| puts word }
p.call("Hello") # => Hello
Procs, as lambdas, keep scope where they've been defined and can change variables:
def say_hi
x = 10
p = Proc.new { "I see and can change x too! x = #{x}" }
p.call
end
puts say_hi # => I see x = 10 too!
Procs and lambdas - difference
Let's consider difference between Procs and lambdas.
1. Lambda strictly checks params number, Proc doesn't
l = ->(a, b) { puts "#{a} and #{b}" }
p = Proc.new { |a, b| puts "#{a} and #{b}" }
Here we have lambda and Proc which accepts two arguments each. Let's try to call lambda with one param:
l.call("foo") # => wrong number of arguments (1 for 2)
Lambda throws an error, because we passed just one argument instead of two.
Now Proc:
p.call("foo") # => foo and
Proc worked. First param it set to "foo", and another to nil
. By this example we see that lambdas require all params to be passed. Procs are more flexible with arguments. If we didn't pass some arguments, it will set them to nil
.
One thing to know: Procs and lambdas behave as methods and accepts default values for arguments:
l = ->(word = "default") { puts word }
l.call # => default
p = proc { |word = "default"| puts word }
p.call # => default
If we defined default values for arguments, we can call lambda as a Proc and don't pass any values at all. In this case default values will be used.
2. return from lambda just returns value. Explicit return from Proc returns value from current scope (for example method) in which it was defined.
We discussed such example with lambda:
def do_math
sum = ->(a, b) { return a + b }
result = sum.call(2, 2)
"Result of 2+2 is #{result}"
end
do_math # => Result of 2+2 is 4
Even after call sum.call(2, 2)
, code inside do_math
continued to execute and we've seen string with a result. So explicit return from lambda worked as any other return form regular method.
Explicit return from Proc works in different way:
def foo
p = Proc.new { return "Returned value from Proc" }
p.call
"Return from foo"
end
puts foo # => Returned value from Proc
As soon as we called p.call
, Proc took control over method foo
and after
explicit return
returned its value and interrupted execution of foo
method. We didn't get "Return from foo" string. Because foo
returned Proc's result of execution.
If we want Proc's return to behave as lambda's, we should remove explicit return
from Proc:
def foo
p = Proc.new { "Returned value from Proc" }
p.call
"Return from foo"
end
puts foo # => Return from foo
In this case after p.call
call, method continued to execute and returned last string from foo
method.
Procs and lambdas as blocks
In recent article we talked about passing blocks to methods. Now we know that lambdas and Procs are blocks of code as well. Good question is: is it possible to pass Procs and lambdas like a block inside method? Answer is: YES!
Let's consider example when method reqires string as a param and also can accept a block. If we pass just string - it prints it as is. If we pass block which decorates that string we call it:
def decorate(str)
block_given? ? yield(str) : str
end
puts decorate("Bare string") # => Bare string
puts decorate("foo") { |str| "Decorated string: #{str}" } # => Decorated string: foo
We could use Proc as a block which we pass into decorate
method:
def decorate(str)
block_given? ? yield(str) : str
end
p = Proc.new { |str| "Decorated string #{str}" }
puts decorate("foo", &p) # => Decorated string: foo
We used &p
to pass Proc as a block for method decorate
. One important note: block passed as a last parameter.
We can pass lambda as a block as well (for lambdas method to_proc
will be called automatically):
func = ->(num) { "#{num}!" }
[1,2,3].map(&func) # => ["1!", "2!", "3!"]
I hope that previous examples were easy to understand. Because if you understand how it works - following example will be easy to understand as well.
I think all of us have seen such code:
["foo", "bar", "baz"].map(&:upcase) # => ["FOO", "BAR", "BAZ"]
This code call method upcase
on each element of array.
We could write it in this way:
["foo", "bar", "baz"].map { |x| x.upcase }
But developers use short version:
["foo", "bar", "baz"].map(&:upcase)
Let's try to understand all "magic" behind this code. So:
- Method
map
accepts block and passes each element of array one by one. &
tells that we want to pass Proc as a block formap
.- We pass symbol
:upcase
. It's not a Proc, soto_proc
method will be called automatically. - Executing
:upcase.to_proc
creates Proc like this one:proc { |x| x.upcase }
- After that Proc is being passed into
map
method.
That's how we call upcase
for each element of an array.
Summary
- To simplify: lambdas and Procs - blocks of code which we can assign to variable and call when we need it by
call
method. - Lambdas strictly control number of arguments which should be passed. Procs don't. If you don't pass all required arguments into Proc it will set them to
nil
. - If call explicit
return
from Proc - it will stop execution and return value from that scope where it was defined. Return from lambda behaves the same way as return from regular method. - We can pass
Proc
as a block using&
. If we pass another type, methodto_proc
will be called automatically.