Closures

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.

Closures and Shared Variables

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.

Closures and Bindings

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.67.22