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
mapaccepts 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_procmethod will be called automatically. - Executing
:upcase.to_proccreates Proc like this one:proc { |x| x.upcase } - After that Proc is being passed into
mapmethod.
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
callmethod. - 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
returnfrom 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
Procas a block using&. If we pass another type, methodto_procwill be called automatically.
