Clausuras y Variables Compartidas

  1. El término clausura se refiere a un objeto que es a la vez invocable como una función y que dispone de un binding o entorno de ejecución para esa función.

  2. El objeto Proc resultante de la creación de un proc o una lambda no sólo nos proporciona un bloque ejecutable: también nos proporciona bindings para todas las variables que son usadas por el bloque
  3. El bloque asociado con la invocación de collect usa 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
    
  4. Si en vez de un proc usamos una lambda, el acceso a n permanece mas allá de multiplier. Es esta característica la que hace que la lambda doubler sea una clausura:
    # 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
    
    doubler encapsula o clausura con la lambda el binding de n
  5. 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.
    
  6. Este código retorna un array de lambdas que son multiplicadores:
    $ cat closure_shared_variables.rb 
    # 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)
    

    $ rvm use 1.8.7
    Using /Users/casiano/.rvm/gems/ruby-1.8.7-p352
    $ ruby closure_shared_variables.rb 
    4
    
    Veamos que ocurre cuando descomentamos la línea # x = nil:
    $ cat closure_shared_variables.rb
    
    # Return an array of lambdas that multiply by the arguments
    def multipliers(*args)
      x = nil                            
      args.map {|x| lambda {|y| x*y }}   # En 1.8 la |x| es la misma x 
    end                                  # local
    
    double,triple = multipliers(2,3)     
    puts double.call(2)    # Prints 6 in Ruby 1.8
    
    En Ruby 1.8 la |x| del argumento del bloque no es local al bloque sino que es la x de multipliers definida en x = nil. Obsérvese que la llamada multipliers(2, 3) retorna un array con dos lambdas. Obtenemos así un comportamiento inesperado:
    $ rvm use 1.8.7
    Using /Users/casiano/.rvm/gems/ruby-1.8.7-p352
    $ rvm list
    
    rvm rubies
    
    => ruby-1.8.7-p352 [ i686 ]
       ruby-1.9.2-head [ x86_64 ]
       ruby-1.9.2-p290 [ x86_64 ]
       ruby-1.9.3-head [ x86_64 ]
    
    $ ruby closure_shared_variables.rb 
    6
    
    Esto es así porque la única x contiene el último valor asignado a ella: 3.

    En este código el problema desaparece con versiones posteriores a la 1.9 porque los argumentos de un bloque son siempre locales:

    $ rvm use 1.9.2
    Using /Users/casiano/.rvm/gems/ruby-1.9.2-p290
    $ ruby closure_shared_variables.rb 
    4
    

Casiano Rodriguez León 2015-01-07