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"