Создать Proc с текущим значением переменной - PullRequest
2 голосов
/ 30 июня 2010

У меня проблема с маленькой надоедливой функцией в классе в библиотеке, которую я не создавал (и, следовательно, не могу редактировать). Вот простой класс с изолированным поведением:

class Foo              # This is a class I cannot
  def setmyproc(&code) # safely edit.
    @prc = Proc.new    # Due to it being in a
  end                  # frustratingly complex
  def callmyproc()     # hierarchy, I don't
    @prc.call          # even know how to reopen
  end                  # it. Consider this class
end                    # set in stone.

Я сталкиваюсь с проблемой, когда пытаюсь выполнить итерацию и сгенерировать массив этих объектов. Я ожидал, что вместо i в Proc будет добавлено другое значение для каждого объекта, но вместо этого происходит то, что переменная i является общей для них.

$bar = []
for i in (0..15)
  $bar[i] = Foo.new
  $bar[i].setmyproc { puts i }
end

$bar[3].callmyproc # expected to print 3
$bar[6].callmyproc # expected to print 6

Выход

  15
  15

Что я могу сделать внутри цикла, чтобы сохранить отдельные значения i для каждого объекта?

Ответы [ 3 ]

3 голосов
/ 30 июня 2010

Используйте это:

$bar = []
(0..15).each do |i|
  $bar[i] = Foo.new
  $bar[i].setmyproc { puts i }
end

$bar[3].callmyproc # prints 3
$bar[6].callmyproc # prints 6

Если вам действительно нужно внести изменения внутри цикла, используйте это (только ruby ​​1.9):

$bar = []
for i in (0..15)
  ->(x) do
    $bar[x] = Foo.new
    $bar[x].setmyproc { puts x }
  end.(i)
end

$bar[3].callmyproc # prints 3
$bar[6].callmyproc # prints 6
2 голосов
/ 30 июня 2010

Блок, который передается в каждый Foo в массиве $ bar, связан с той же переменной i. Каждый раз, когда вы отправляете callmyproc, используется текущее значение i в исходной области.

$bar[3].callmyproc
=> 15
$bar[6].callmyproc
=> 15

i = 42

$bar[3].callmyproc
=> 42
$bar[6].callmyproc
=> 42

Вам нужно отправить разные объекты в каждый процесс:

0.upto(15) do |i|
  $bar[i] = Foo.new
  $bar[i].setmyproc { i.to_i }
end

$bar[3].callmyproc
 => 3 
$bar[6].callmyproc
 => 6 
1 голос
/ 30 июня 2010

Хорошо, так что, прежде всего, добро пожаловать в замыкания:)

Замыкание - это фрагмент кода, который вы можете передавать как переменную, это простая часть. Другая сторона заключается в том, что замыкание поддерживает область, в которой оно было вызвано.

Что на самом деле происходит, так это то, что, когда вы храните свои процы, каждый берет ссылку на n. несмотря на то, что вы выходите за рамки цикла for, эта ссылка на n по-прежнему остается неизменной, и каждый раз, когда вы выполняете свои процессы, они выводят окончательное значение n. Проблема здесь в том, что каждая итерация не в своей области видимости.

То, что Адриан предложил сделать, это поменять цикл for на блок range.each. Разница в том, что каждая итерация имеет свою собственную область, и это то, что связано с процедурой

$bar = []
(0..15).each do |i|
  #each i in here is local for this block
  $bar[i] = Foo.new
  $bar[i].setmyproc { puts i }
end

Это действительно не простая вещь, чтобы обернуть вашу голову, но это одна из тех вещей, которые будут продолжать сбивать вас с толку, пока вы действительно не получите это. Я, вероятно, проделал ужасную работу, объясняя это, если бы он не превращался в гель, я потратил бы немного времени на поиски работы затворов с областью действия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...