Utiliser ‘return’ dans un bloc Ruby

J’essaie d’utiliser Ruby 1.9.1 pour un langage de script intégré, afin que le code “utilisateur final” soit écrit dans un bloc Ruby. Un problème avec ceci est que je voudrais que les utilisateurs puissent utiliser le mot-clé ‘return’ dans les blocs, afin qu’ils n’aient pas à se soucier des valeurs de retour implicites. Dans cet esprit, c’est 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, je reçois une erreur LocalJumpError. Je sais 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ère 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.

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 `
' irb(main):009:0> thing { break 6 * 7 } => 42 irb(main):011:0> thing { next 6 * 7 } value=42 => nil
  • return retourne toujours de la méthode, mais si vous testez cet extrait dans irb, vous n’avez pas de méthode, c’est pourquoi vous avez LocalJumpError
  • break retourne la valeur du bloc et termine son appel. Si votre bloc a été appelé par yield ou .call , alors break pauses de cet iterator également
  • next retourne la valeur de block et termine son appel. Si votre bloc a été appelé par yield ou .call , next valeur next renvoyée à la ligne où le yield été appelé

Vous ne pouvez pas faire ça en Ruby.

Le mot clé return retourne toujours de la méthode ou de 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 pour revenir de la méthode appelante ou lambda.

Le Rubyspec démontre qu’il s’agit bien du 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 # ... 

Vous le regardez du mauvais sharepoint vue. C’est un problème, pas le lambda.

 def thing(*args, &block) block.call.tap do |value| puts "value=#{value}" end end thing { 6 * 7 } 

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 

Je crois 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 les retours dans leurs processus sans conséquences, le self est préservé, etc.

L’avantage d’utiliser Thread est que, dans certains cas, vous n’obtiendrez pas le LocalJumpError – et le retour aura lieu à l’endroit le plus inattendu (à côté d’une méthode de haut niveau, ignorant de manière inattendue le rest du corps).

Le principal inconvénient est la surcharge potentielle (vous pouvez remplacer le Thread + join avec le yield si cela est suffisant dans votre scénario).

J’ai eu le même problème en écrivant un DSL pour un framework web en ruby ​​… (le framework web Anorexic va basculer!) …

de toute façon, j’ai fouillé les composants internes de Ruby et trouvé une solution simple en utilisant le LocalJumpError renvoyé lorsqu’un appel Proc retourne … il fonctionne bien dans les tests jusqu’à présent, mais je ne suis pas sûr que ce soit complet:

 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 

la déclaration if dans le segment de secours pourrait probablement ressembler à ceci:

 if e.is_a? LocalJumpError 

mais c’est un territoire inconnu pour moi, alors je vais m’en tenir à ce que j’ai testé jusqu’à présent.

J’ai trouvé un moyen, mais cela implique de définir une méthode comme une étape intermédiaire:

 def thing(*args, &block) define_method(:__thing, &block) puts "value=#{__thing}" end thing { return 6 * 7 }