anthropomorphic anthropomorphic - 6 months ago 11
Javascript Question

Capturing variables in Ruby methods

In CoffeeScript:

f = ->
v = 5
g = ->
v
g()

f() # returns 5 as expected


In Ruby:

def f
v = 5
def g
v # undefined local variable or method `v' for main:Object (NameError)
end
g
end
f


Okay, so apparently JavaScript functions are set up to capture variables in the scope they are created in, but Ruby's methods are not. Is there a way to make Ruby methods behave like JavaScript functions in this regard?

Answer

Ruby has script scope, module/class/method definition scope and block scope. Only blocks create nested scopes. So, you need to use a block to define your method. Thankfully, there is a method for defining methods that takes a block:

def f
  v = 5
  define_method :g do
    v
  end
  g
end
f
# => 5

However, note that this does not do what you think it does (and neither does your original code). It does not define a method g nested in method f. Ruby doesn't have nested methods. Methods always belong to modules (classes are modules), they cannot belong to methods.

What this does is define a method f, which when you run it defines a method g and then calls that method.

Observe:

methods.include?(:g)
# => true

You have now defined a new top-level method (actually a private instance method of Object) called g, and you will define it over and over and over again, everytime f gets called.

What you probably want is a lambda:

def f
  v = 5
  g = -> { v }
  g.()
end
f
# => 5

In a comment to another answer you wrote:

Okay, so I'm messing around with lambdas, and I noticed that anything I do to v inside of g is not reflected in v once g returns. Is there a way I can make my changes to v stick?

def f
  v = 5
  g = -> { v = 'Hello' }
  g.()
  v
end
f
# => 'Hello'

As you can see, the change to v does "stick" after g returns.

Comments