ruby: un blocco può influenzare le variabili locali in un metodo?
Domanda
Sto solo imparando ruby ??e sto cercando di capire l'ambito del codice eseguito in blocchi. Ad esempio, voglio essere in grado di creare un blocco che influisce sul metodo a cui è collegato, in questo modo:
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"
}
In questo caso non voglio affatto modificare il blocco: voglio poterlo scrivere usando semplici riferimenti a variabili e nessun parametro. Solo modificando il metodo "test" nell'esempio precedente , è possibile accedere alle variabili definite nel blocco?
Ancora una volta, l'obiettivo è lasciare il blocco non modificato, ma essere in grado di accedere alle variabili create da 'test' dopo l'esecuzione del blocco.
Soluzione
Innanzitutto, block.call ()
viene eseguito con yield
e non è necessario il parametro & amp; block
che modo.
Normalmente non puoi fare quello che vuoi, i blocchi vengono associati quando vengono creati e all'interno del blocco puoi vedere le variabili locali definite in quel momento; il modo più semplice per fare quello che vuoi, che non è come userai normalmente i blocchi, è questo:
def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end
test() {
foo="this is foo"
}
Ma questo è solo un effetto collaterale perché foo
è " reso " dal blocco. Se invece lo fai:
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"
}
Noterai che fa qualcosa di diverso.
Ecco più 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"
}
Funzionerebbe in qualche modo, ma si interrompe se si rimuove foo = " prima del test "
perché foo
diventa una variabile locale nel blocco e non esiste in l'associazione.
Riepilogo: non è possibile accedere alle variabili locali da un blocco, solo i locali in cui è stato definito il blocco e il valore di ritorno del blocco.
Anche questo non funzionerà:
def test(&block)
eval "foo = 'go fish'", block.binding
block.call
bar = eval "foo", block.binding
puts bar
end
perché il foo
nell'associazione è diverso da quello locale nel blocco (non lo sapevo, grazie).
Altri suggerimenti
No, un blocco non può influenzare le variabili locali nel luogo in cui viene chiamato.
I blocchi in Ruby sono chiusure , il che significa che catturano l'ambito attorno a loro quando vengono creati. Le variabili che sono visibili quando si crea il blocco sono quelle che vede. Se ci fosse un foo
e bar
nella parte superiore del tuo codice, al di fuori di qualsiasi metodo, quel blocco cambierebbe quelli quando veniva chiamato.
Puoi fare quello che vuoi essendo un po 'più prolisso:
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"
}
Puoi creare metodi come attr_accessor
che genereranno setter appropriati (i metodi foo
e bar
).
def test(&block)
foo = yield
puts "in test, foo is #{foo}"
end
test { "this is foo" }
stampa in prova, foo è questo è foo
Il valore di rendimento è il valore del blocco.
È anche possibile passare parametri per produrre, a cui è possibile accedere dal blocco usando | param, un altro | all'inizio del blocco.
Inoltre, controlla procs.
foo = "this is foo"
p = Proc.new { "foo is #{foo}" }
p.call
Stampa " foo è questo è foo "
def test(p)
p.call
end
test p
Stampa " foo è questo è foo "
def test2(p)
foo = "monkey"
p.call
end
test2 p
Stampa " foo è questo è foo "