J'essaie d'utiliser Ruby 1.9.1 pour un langage de script intégré, de sorte que le code "utilisateur final" soit écrit dans un bloc Ruby. Un problème avec ceci est que j'aimerais que les utilisateurs puissent utiliser le mot-clé 'return' dans les blocs, afin qu'ils n'aient pas à s'inquiéter des valeurs de retour implicites. Dans cet esprit, voici le genre de chose que j'aimerais pouvoir faire:
def thing(*args, &block)
value = block.call
puts "value=#{value}"
end
thing {
return 6 * 7
}
Si j'utilise «return» dans l'exemple ci-dessus, j'obtiens un LocalJumpError. Je suis conscient que c'est parce que le bloc en question est un Proc et non un lambda. Le code fonctionne si je supprime «return», mais je préférerais vraiment pouvoir utiliser «return» dans ce scénario. Est-ce possible? J'ai essayé de convertir le bloc en lambda, mais le résultat est le même.
la source
Réponses:
Utilisez simplement
next
dans ce contexte:$ irb irb(main):001:0> def thing(*args, &block) irb(main):002:1> value = block.call irb(main):003:1> puts "value=#{value}" irb(main):004:1> end => nil irb(main):005:0> irb(main):006:0* thing { irb(main):007:1* return 6 * 7 irb(main):008:1> } LocalJumpError: unexpected return from (irb):7:in `block in irb_binding' from (irb):2:in `call' from (irb):2:in `thing' from (irb):6 from /home/mirko/.rvm/rubies/ruby-1.9.1-p378/bin/irb:15:in `<main>' irb(main):009:0> thing { break 6 * 7 } => 42 irb(main):011:0> thing { next 6 * 7 } value=42 => nil
return
renvoie toujours de la méthode, mais si vous testez cet extrait de code dans irb, vous n'avez pas de méthode, c'est pourquoi vous avezLocalJumpError
break
renvoie la valeur du bloc et met fin à son appel. Si votre bloc a été appelé paryield
ou.call
, alorsbreak
s'interrompt également avec cet itérateurnext
renvoie la valeur du bloc et termine son appel. Si votre bloc a été appelé paryield
ou.call
,next
renvoie la valeur à la ligne où ayield
été appeléla source
Vous ne pouvez pas faire cela dans Ruby.
Le
return
mot clé retourne toujours de la méthode ou lambda dans le contexte actuel. Dans les blocs, il reviendra de la méthode dans laquelle la fermeture a été définie . Il ne peut pas être fait revenir de la méthode appelante ou de lambda.Le Rubyspec démontre que c'est en effet le comportement correct pour Ruby (certes pas une implémentation réelle, mais vise une compatibilité totale avec C Ruby):
describe "The return keyword" do # ... describe "within a block" do # ... it "causes the method that lexically encloses the block to return" do # ... it "returns from the lexically enclosing method even in case of chained calls" do # ...
la source
Vous le regardez du mauvais point de vue. C'est un problème de
thing
, pas de lambda.def thing(*args, &block) block.call.tap do |value| puts "value=#{value}" end end thing { 6 * 7 }
la source
Où la chose est-elle invoquée? Êtes-vous dans une classe?
Vous pouvez envisager d'utiliser quelque chose comme ceci:
class MyThing def ret b @retval = b end def thing(*args, &block) implicit = block.call value = @retval || implicit puts "value=#{value}" end def example1 thing do ret 5 * 6 4 end end def example2 thing do 5 * 6 end end end
la source
J'ai eu le même problème d'écriture d'un DSL pour un framework web en ruby ... (le framework web Anorexic va basculer!) ...
Quoi qu'il en soit, j'ai creusé dans les composants internes de ruby et j'ai trouvé une solution simple en utilisant le LocalJumpError renvoyé lorsqu'un Proc appelle le retour ... cela fonctionne bien dans les tests jusqu'à présent, mais je ne suis pas sûr que ce soit une preuve complète:
def thing(*args, &block) if block block_response = nil begin block_response = block.call rescue Exception => e if e.message == "unexpected return" block_response = e.exit_value else raise e end end puts "value=#{block_response}" else puts "no block given" end end
l'instruction if dans le segment de sauvetage pourrait probablement ressembler à ceci:
if e.is_a? LocalJumpError
mais c'est un territoire inconnu pour moi, donc je vais m'en tenir à ce que j'ai testé jusqu'à présent.
la source
Je pense que c'est la bonne réponse, malgré les inconvénients:
def return_wrap(&block) Thread.new { return yield }.join rescue LocalJumpError => ex ex.exit_value end def thing(*args, &block) value = return_wrap(&block) puts "value=#{value}" end thing { return 6 * 7 }
Ce hack permet aux utilisateurs d'utiliser le retour dans leurs processus sans conséquences, l'auto est préservé, etc.
L'avantage d'utiliser Thread ici est que dans certains cas, vous n'obtiendrez pas le LocalJumpError - et le retour se produira à l'endroit le plus inattendu (à côté d'une méthode de niveau supérieur, sautant de manière inattendue le reste de son corps).
Le principal inconvénient est la surcharge potentielle (vous pouvez remplacer la jointure Thread + par juste
yield
si cela suffit dans votre scénario).la source
J'ai trouvé un moyen, mais il s'agit de définir une méthode comme étape intermédiaire:
def thing(*args, &block) define_method(:__thing, &block) puts "value=#{__thing}" end thing { return 6 * 7 }
la source