Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby equivalent of python nonlocal

I am trying to write a closure in Ruby. This is the code written in Python:

def counter():
    x = 0
    def increment(y):
        nonlocal x
        x += y
        print(x)
    return increment

Is there a "nonlocal" equivalent in Ruby so I can access and make changes to the variable x from inside increment?

like image 537
jazzyfresh Avatar asked Dec 09 '12 01:12

jazzyfresh


People also ask

Is nonlocal good practice Python?

nonlocal completely encapsulates that information in a namespace dedicated to the particular run of your outer function. There is really no downside here. Using instance attributes makes that information available to anyone using the instance.

What does nonlocal mean Python?

Definition and Usage The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. Use the keyword nonlocal to declare that the variable is not local.

Is nonlocal necessary Python?

The nonlocal statement. The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first.


2 Answers

The nonlocal keyword tells Python which variables to capture. In Ruby, you don't need such a keyword: all variables are captured unless explicitly mentioned otherwise.

So, the Ruby equivalent to your Python code translates almost directly:

counter = -> {
  x = 0
  ->y {
    x += y
    puts x
  }
}

i = counter.()

i.(2)
# 2

i.(3)
# 5

It would probably be more idiomatic to use a method for counter, though:

def counter
  x = 0
  ->y {
    x += y
    puts x
  }
end

i = counter

i.(2)
# 2

i.(3)
# 5
like image 136
Jörg W Mittag Avatar answered Oct 26 '22 14:10

Jörg W Mittag


Since there is objection to using an object, why not just use a lambda?

counter_generator = ->(){
  x ||= 0
  ->(y){
    x += y
    puts x
  }
}

i = counter_generator.call
=> #<Proc:0x00000100867508@(irb):17 (lambda)>
i.call(1)
1
=> nil
i.call(1)
2
=> nil

Note that the incrementor actually returns nil because you've only specified to output the value of x, not to return it.

like image 29
ian Avatar answered Oct 26 '22 15:10

ian