objetos - proc ruby
ruby: ¿puede un bloque afectar variables locales en un método? (4)
Estoy aprendiendo ruby y tratando de entender el alcance del código ejecutado en bloques. Por ejemplo, quiero poder crear un bloque que afecte el método al que está conectado, así:
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"
}
En este caso, no quiero tener que modificar el bloque en absoluto; quiero poder escribirlo usando referencias de variables simples y sin parámetros. Solo haciendo cambios al método de ''prueba'' en el ejemplo anterior , ¿es posible acceder a las variables definidas en el bloque?
De nuevo, el objetivo es dejar el bloque sin modificar, pero poder acceder a las variables creadas desde ''prueba'' después de que se ejecute el bloque.
def test(&block)
foo = yield
puts "in test, foo is #{foo}"
end
test { "this is foo" }
impresiones in test, foo is this is foo
El valor del rendimiento es el valor del bloque.
También puede pasar parámetros para ceder, a los que luego se puede acceder mediante el bloque usando | param, otro | al comienzo del bloque.
Además, mira los procs.
foo = "this is foo"
p = Proc.new { "foo is #{foo}" }
p.call
Imprime "foo is this is foo"
def test(p)
p.call
end
test p
Imprime "foo is this is foo"
def test2(p)
foo = "monkey"
p.call
end
test2 p
Imprime "foo is this is foo"
No, un bloque no puede afectar las variables locales en el lugar donde se llama.
Los bloques en Ruby son cierres , lo que significa que capturan el alcance a su alrededor cuando se crean. Las variables que son visibles cuando crea el bloque son las que ve. Si hubiera un foo
y una bar
en la parte superior de tu código, fuera de cualquier método, ese bloque los cambiaría cuando se llamara.
Puedes hacer lo que quieras siendo un poco más detallado:
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"
}
Puede crear métodos como attr_accessor
que generarán un setter apropiado (los métodos foo
y bar
).
En primer lugar, block.call()
se realiza con yield
, y no necesita el parámetro &block
esa manera.
Normalmente no puede hacer lo que quiere, los bloques están vinculados cuando se crean, y dentro del bloque puede ver las variables locales definidas en ese momento; La forma más fácil de hacer lo que desea, que no es cómo usará los bloques normalmente, es esta:
def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end
test() {
foo="this is foo"
}
Pero eso es solo un efecto secundario porque foo
es "devuelto" por el bloque. Si en cambio haces esto:
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"
}
Notarás que hace algo diferente.
Aquí hay más magia:
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"
}
Eso funcionaría, pero se rompe si eliminas foo = "before test"
porque foo
convierte en una variable local en el bloque y no existe en el enlace.
Resumen: no se puede acceder a las variables locales desde un bloque, solo los locales donde se definió el bloque y el valor de retorno del bloque.
Incluso esto no funcionará:
def test(&block)
eval "foo = ''go fish''", block.binding
block.call
bar = eval "foo", block.binding
puts bar
end
porque el foo
en el enlace es diferente del local en el bloque (no lo sabía, gracias).