Рубиновый эквивалент нелокального питона

Я пытаюсь написать закрытие в Ruby. Это код, написанный на Python:

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

Есть ли "нелокальный" эквивалент в Ruby, чтобы я мог получить доступ и внести изменения в переменную x изнутри приращения?

3 ответа

Решение

nonlocal Ключевое слово сообщает Python, какие переменные для захвата. В Ruby вам не нужно такое ключевое слово: все переменные фиксируются, если явно не указано иное.

Итак, эквивалентный Ruby вашему коду Python переводится почти напрямую:

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

i = counter.()

i.(2)
# 2

i.(3)
# 5

Вероятно, было бы более идиоматичным использовать метод для counter, хоть:

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

i = counter

i.(2)
# 2

i.(3)
# 5

Поскольку существует возражение против использования объекта, почему бы просто не использовать лямбду?

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

Обратите внимание, что инкрементор фактически возвращает nil, потому что вы указали только вывод значения x, а не его возвращение.

Может быть что-то вроде:

class CGroup
  def counter
    @x ||= 0
    lambda do |y|
      @x += y
    end
  end
end

Затем:

group = CGroup.new
c = group.counter
c.call(1)
=> 1
c.call(1)
=> 2

Я не знаю о прямом аналоге для Python nonlocal,

РЕДАКТИРОВАТЬ: переменная экземпляра не требуется, и то же самое может быть достигнуто с помощью переменной, локальной для метода. Это делает класс излишним, хотя многое в Ruby происходит в контексте объекта.

Другие вопросы по тегам