What are Procs and lambdas: Closure in Ruby
First: What is a closure? (Somehow my English degree didn’t teach me this :)
Closures allow declarative programming languages – specifically, languages that do not normally maintain state – to persist data.
They are found in many popular languages including
* JavaScript
* Ruby
* Python
* C# and
* Objective-C.
In Ruby, every Proc
object is a closure, which means that each code block you write captures references to data from its surrounding scope for later use. (Source). The core properties of closures, and by extension Ruby procs and lambdas, are:
You can pass
Procs
and lambdas around like objects (to be called later).Procs
and lambdas remember the values of all the variables that were in scope when the function was created.Procs
and lambdas are then able to access those variables when they’re called even if those variables are no longer be in scope.
Let’s dive into some code to examine scope vis a vis procs and lambdas:
def proc_can_see_outer_scope_locals
y = 10
lambda { p defined?(y) }.call
end
proc_can_see_outer_scope_locals #=> "local-variable"
def proc_can_modify_outer_scope_locals
y = 10
lambda { y = 20 }.call
p y
end
proc_can_modify_outer_scope_locals #=> 20
def proc_destroys_block_local_vars_on_exit
lambda { y = 10 }.call
p defined?(y)
end
proc_destroys_block_local_vars_on_exit #=> nil
# Code snippet courtesy of the Practicing Rubyist.
For the record, Procs can be executed 3 ways.
# 1 call method
puts Proc.new{|x|"blah1"*x}.call(2)
# 2 array syntax
puts Proc.new{|x|"blah1"*x}[2]
# 3 dot syntax
puts Proc.new{|x|"blah1"*x}.(2)
What are the differences between Procs
and lambdas?
Procs and lambdas behave differently upon being returned. As soon as procs are returned, they will escape the function.
For example, if you call return within a method, as soon as the proc hits the method is escaped and that value is returned. Nothing after the initial return
will be called.
def my_method
puts "before proc"
my_proc = Proc.new do
puts "inside proc"
return
end
my_proc.call
puts "after proc"
end
my_method
#=> before proc
#=> inside proc
# code snippets courtesy of Alan Skorks.
The final puts
in the method was never executed because the return
within my_proc.call
dumped us out of the method.
Whereas a lambda will let the function continue after the return value, until the whole function has been run.
def my_method
puts "before proc"
my_proc = lambda do
puts "inside proc"
return
end
my_proc.call
puts "after proc"
end
my_method
#=> before proc
#=> inside proc
#=> after proc
Possible downside with lambdas is that they can leak memory. This happens when local variables, which would normally be garbage collected by Ruby’s native gc, are instead persisted by lambda’s reference to them.
Resources for further reading:
- Skorks on Closures, and Skorks on Procs and lambdas.
- Practicing Ruby
- JavaScript 101 is garbaggio #=> find something better.