Ruby: может ли блок влиять на локальные переменные в методе?

Я просто изучаю ruby ​​и пытаюсь понять объем кода, выполняемого в блоках. Например, я хочу иметь возможность создать блок, который влияет на метод, к которому он привязан, например:

def test(&block)
  block.call() if block_given?
  puts "in test, foo is #{foo}"
  puts "in test, bar is #{bar}"
end

test() {
  foo="this is foo"
  bar="this is bar"
}

В этом случае я не хочу вообще изменять блок - я хочу, чтобы он мог писать его с помощью простых ссылок на переменные и без параметров. Только путем внесения изменений в метод "test" в приведенном выше примере, можно ли получить доступ к переменным, определенным в блоке?

Опять же, цель состоит в том, чтобы оставить блок немодифицированным, но иметь возможность доступа к созданным переменным из "теста" после выполнения блока.

Ответ 1

Прежде всего, block.call() выполняется с помощью yield, и вам не нужен параметр &block.

Обычно вы не можете делать то, что хотите, блоки привязаны, когда они созданы, а внутри блока вы можете увидеть локальные переменные, определенные в этот момент; самый простой способ сделать то, что вы хотите, что не так, как вы будете использовать блоки обычно:

def test()
  foo = yield if block_given?
  puts "in test, foo is #{foo}"
end

test() {
  foo="this is foo"
}

Но это только побочный эффект, потому что foo "возвращается" блоком. Если вы сделаете это:

def test()
  foo = yield if block_given?
  puts "in test, foo is #{foo}"
end

test() {
  foo="this is foo"
  "ha ha, no foo for you"
}

Вы заметите, что он делает что-то другое.

Здесь больше магии:

def test(&block)
   foo = eval "foo", block.binding
   puts foo
   block.call
   foo = eval "foo", block.binding
   puts foo
end

foo = "before test"
test() {
  foo = "after test"
  "ha ha, no foo for you"
}

Это будет работать, но это сломается, если вы удалите foo = "before test", потому что foo становится локальной переменной в блоке и не существует в привязке.

Сводка: вы не можете получить доступ к локальным переменным из блока, только локали, где был определен блок, и возвращаемое значение блока.

Даже это не сработает:

def test(&block)
   eval "foo = 'go fish'", block.binding
   block.call
   bar = eval "foo", block.binding
   puts bar
end

потому что foo в привязке отличается от локального в блоке (я этого не знал, спасибо).

Ответ 2

Нет, блок не может влиять на локальные переменные в том месте, где он вызывался.

Блоки в Ruby - это закрытие, что означает, что они захватывают область вокруг них, когда они создаются. Переменные, которые видны при создании блока, являются теми, которые он видит. Если в верхней части вашего кода были foo и bar, вне какого-либо метода этот блок изменил бы те, когда он был вызван.

Ответ 3

Вы можете делать то, что хотите, будучи немного более подробным:

class Test
  def foo(t)
    @foo = t
  end
  def bar(t)
    @bar = t
  end
  def test(&block)
    self.instance_eval &block if block_given?
    puts "in test, foo is #{@foo}"
    puts "in test, bar is #{@bar}"
  end
end

Test.new.test() {
  foo "this is foo"
  bar "this is bar"
}

Вы можете создавать такие методы, как attr_accessor, которые будут генерировать подходящий сеттер (методы foo и bar).

Ответ 4

def test(&block)
  foo = yield
  puts "in test, foo is #{foo}"
end

test { "this is foo" }

печатает in test, foo is this is foo

Значение выхода - это значение блока.

Вы также можете передать параметры для выхода, которые затем могут быть доступны блоком с помощью | param, another | в начале блока.

Кроме того, проверьте procs.

foo = "this is foo"
p = Proc.new { "foo is #{foo}" }
p.call

Печать "foo is this is foo"

def test(p) 
  p.call
end

test p

Печать "foo is this is foo"

def test2(p)
  foo = "monkey"
  p.call
end

test2 p

Печать "foo is this is foo"