2009-01-07 11 views
10

Je suis en train d'apprendre le rubis et d'essayer de comprendre la portée du code exécuté en blocs. Par exemple, je veux être en mesure de créer un bloc qui affecte la méthode qu'il est attaché à, comme ceci:ruby: un bloc peut-il affecter les variables locales dans une méthode?

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" 
} 

Dans ce cas, je ne veux pas avoir à modifier le bloc du tout - Je veux être capable de l'écrire en utilisant des références de variables simples et pas de paramètres. Seulement en modifiant la méthode 'test' dans l'exemple ci-dessus, est-il possible d'accéder aux variables définies dans le bloc?

Encore une fois, le but est de laisser le bloc non modifié, mais être en mesure d'accéder aux variables créées depuis 'test' après l'exécution du bloc.

Répondre

10

Tout d'abord, block.call() est fait avec yield, et vous n'avez pas besoin du paramètre &block de cette façon.

Vous ne pouvez normalement pas faire ce que vous voulez, les blocs sont liés quand ils sont créés, et à l'intérieur du bloc vous pouvez voir les variables locales définies à ce moment-là; la meilleure façon de faire ce que vous voulez, ce qui est la façon dont vous utiliserez les blocs normalement, est la suivante:

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

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

Mais ce n'est un effet secondaire parce foo est « retourné » par le bloc. Si vous faites plutôt cela:

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" 
} 

Vous remarquerez que cela fait quelque chose de différent.

est ici plus magique:

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" 
} 

Ce serait genre de travail, mais il casse si vous supprimez foo = "before test" parce foo devient une variable locale dans le bloc et n'existe pas dans la liaison.

Résumé: vous ne pouvez pas accéder aux variables locales à partir d'un bloc, seulement les sections locales où le bloc a été défini et la valeur de retour du bloc.

Même cela ne fonctionnera pas:

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

parce que le foo dans la liaison est différent du local dans le bloc (je ne savais pas, merci).

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

test { "this is foo" } 

impressions in test, foo is this is foo

La valeur de rendement est la valeur du bloc.

Vous pouvez également transmettre des paramètres à céder, qui peuvent alors être accédés par le bloc en utilisant | param, un autre | au début du bloc.

Également, consultez procs.

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

Prints "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"

+0

Ceci est trompeur, vous n'accédez pas aux sections locales dans le bloc comme le dit la question, juste la valeur de retour du bloc. –

3

Non, un bloc ne peut pas affecter les variables locales à l'endroit où on l'appelle.

Les blocs en Ruby sont et, ce qui signifie qu'ils capturent la portée autour d'eux lorsqu'ils sont créés. Les variables visibles lorsque vous créez le bloc sont celles qu'il voit. S'il y avait un foo et bar en haut de votre code, en dehors de toute méthode, ce bloc changerait ceux quand il a été appelé.

2

Vous pouvez faire ce que vous voulez en étant un peu plus bavard:

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" 
} 

Vous pouvez créer des méthodes telles que attr_accessor qui généreront setter apropriate (les méthodes foo et bar).

Questions connexes