Pregunta

Coming from a JavaScript background I've become accustomed to being able to use JavaScript's dynamic scope to encapsulate values in a function. For example:

function Dog( firstname, lastname ) {

  this.fullname = firstname + lastname

  return {
    say_name: function () {
      return fullname;
    }
  }

}

Now in Ruby I'm not so sure something like this would work too well:

class Foo

  attr_accessor :bar, :baz

  def initialize bar, baz
    @bar = bar
    @baz = baz
  end

  def give_me_a_proc
    return Proc.new { @bar + @baz }
  end
end

Can anyone give a quick explanation of how scope works in Ruby? If I call the Proc returned from give_me_a_proc, will it still have access to its define-time scope?

Also do the values become fixed once I define the proc or do any changes made in Foo get carried down to the Proc even after it has been defined?

¿Fue útil?

Solución

In Ruby a proc is a closure. In Javascript, a function is a closure. The idea of a closure is that it "closes over" the environment in which it was defined. In Ruby the variables in the proc can still be changed, even by code living outside of the proc, and the new values will be reflected within the proc(see peakxu for demonstration). Not sure if Javascript closures work this way.

Otros consejos

Yes, it will still have access to definition time scope. See documentation for Proc class.

Changes are carried down to the Proc post-definition. Results from irb run with your class.

> foo = Foo.new(1, 2)
 => #<Foo:0x007f9002167080 @bar=1, @baz=2> 
> quux = foo.give_me_a_proc
 => #<Proc:0x007f900214f688@(irb):11> 
> quux.call
 => 3 
> foo.bar = 3
 => 3 
> quux.call
 => 5 

Ruby procs and lambdas are closures. When you do return Proc.new { @bar + @baz } you are really capturing self, which is how instance variables are looked up. Ruby blocks are also closures. Ruby does let you change the variables and the changes will propagate up to the calling scope, assuming that the calling scope still exists:

@hi = 0
def get_hi()
  lambda {@hi = 42}
end
get_hi.call()
@hi #=> 42

Note: Unless your proc has very lax argument requirements (it doesn't care if it gets any arguments, how many, sort of like C's int f(void)), use lambda instead of Proc.new. lambda checks to make sure that you get the right number of arguments.

Ruby has a method to get closure from object, it suits very nice to your example:

class Foo
  # reopen Foo class
  def get_sum
    @bar + @baz
  end
end

m = Foo.new(5,20).method(:get_sum)
m.call #=> 25

The m is the Method object and it acts as a closure in Foo’s instance, so instance variables and the value of self remain available.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top