In Ruby, procs and lambdas are
closures. The term “closure” comes from the early
days of computer science; it refers to an object that is both an
invocable function and a variable binding for that function. When you
create a proc or a lambda, the resulting Proc
object holds not just the executable
block but also bindings for all the variables used by the block.
You already know that blocks can use local variables and method
arguments that are defined outside the block. In the following code, for
example, the block associated with the collect
iterator uses the method argument
n
:
# multiply each element of the data array by n def multiply(data, n) data.collect {|x| x*n } end puts multiply([1,2,3], 2) # Prints 2,4,6
What is more interesting, and possibly even surprising, is that if
the block were turned into a proc or lambda, it could access n
even after the method to which it is an
argument had returned. The following code demonstrates:
# Return a lambda that retains or "closes over" the argument n def multiplier(n) lambda {|data| data.collect{|x| x*n } } end doubler = multiplier(2) # Get a lambda that knows how to double puts doubler.call([1,2,3]) # Prints 2,4,6
The multiplier
method returns a
lambda. Because this lambda is used outside of the scope in which it is
defined, we call it a closure; it encapsulates or “closes over” (or just
retains) the binding for the method argument n
.
It is important to understand that a closure does not just retain the value of the variables it refers to—it retains the actual variables and extends their lifetime. Another way to say this is that the variables used in a lambda or proc are not statically bound when the lambda or proc is created. Instead, the bindings are dynamic, and the values of the variables are looked up when the lambda or proc is executed.
As an example, the following code defines a method that returns two lambdas. Because the lambdas are defined in the same scope, they share access to the variables in that scope. When one lambda alters the value of a shared variable, the new value is available to the other lambda:
# Return a pair of lambdas that share access to a local variable. def accessor_pair(initialValue=nil) value = initialValue # A local variable shared by the returned lambdas. getter = lambda { value } # Return value of local variable. setter = lambda {|x| value = x } # Change value of local variable. return getter,setter # Return pair of lambdas to caller. end getX, setX = accessor_pair(0) # Create accessor lambdas for initial value 0. puts getX[] # Prints 0. Note square brackets instead of call. setX[10] # Change the value through one closure. puts getX[] # Prints 10. The change is visible through the other.
The fact that lambdas created in the same scope share access to variables can be a feature or a source of bugs. Any time you have a method that returns more than one closure, you should pay particular attention to the variables they use. Consider the following code:
# Return an array of lambdas that multiply by the arguments def multipliers(*args) x = nil args.map {|x| lambda {|y| x*y }} end double,triple = multipliers(2,3) puts double.call(2) # Prints 6 in Ruby 1.8
This multipliers
method uses
the map
iterator and a block to
return an array of lambdas (created inside the block). In Ruby 1.8,
block arguments are not always local to the block (see Blocks and Variable Scope), and so all of the lambdas that are
created end up sharing access to x
,
which is a local variable of the multipliers
method. As noted above, closures
don’t capture the current value of the variable: they capture the
variable itself. Each of the lambdas created here share the variable
x
. That variable has only one
value, and all of the returned lambdas use that same value. That is
why the lambda we name double
ends
up tripling its argument instead of doubling it.
In this particular code, the issue goes away in Ruby 1.9 because block arguments are always block-local in that version of the language. Still, you can get yourself in trouble any time you create lambdas within a loop and use loop variables (such as an array index) within the lambda.
The Proc
class defines
a method named binding
. Calling this method on a proc or
lambda returns a Binding
object
that represents the bindings in effect for that closure.
A Binding
object doesn’t have
interesting methods of its own, but it can be used as the second
argument to the global eval
function (see Evaluating Strings and Blocks), providing a context in which to evaluate a string of Ruby code.
In Ruby 1.9, Binding
has its own eval
method, which you
may prefer to use. (Use ri
to learn
more about Kernel.eval
and Binding.eval
.)
The use of a Binding
object
and the eval
method gives us a back
door through which we can manipulate the behavior of a closure. Take
another look at this code from earlier:
# Return a lambda that retains or "closes over" the argument n def multiplier(n) lambda {|data| data.collect{|x| x*n } } end doubler = multiplier(2) # Get a lambda that knows how to double puts doubler.call([1,2,3]) # Prints 2,4,6
Now suppose we want to alter the behavior of doubler
:
eval("n=3", doubler.binding) # Or doubler.binding.eval("n=3") in Ruby 1.9 puts doubler.call([1,2,3]) # Now this prints 3,6,9!
As a shortcut, the eval
method allows you to pass a Proc
object directly instead of passing the Binding
object of the Proc
. So we could replace the eval
invocation above with:
eval("n=3", doubler)
Bindings are not only a feature of closures. The Kernel.binding
method returns a Binding
object that
represents the bindings in effect at whatever point you happen to call
it.
18.191.93.12