Utilisation de 'return' dans un bloc Ruby

87

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.

MetaFu
la source
pourquoi voulez-vous éviter une valeur de retour implicite?
marcgg
@marcgg - J'ai une question connexe ici - stackoverflow.com/questions/25953519/… .
sid smith

Réponses:

171

Utilisez simplement nextdans 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 avez LocalJumpError
  • breakrenvoie la valeur du bloc et met fin à son appel. Si votre bloc a été appelé par yieldou .call, alors breaks'interrompt également avec cet itérateur
  • nextrenvoie la valeur du bloc et termine son appel. Si votre bloc a été appelé par yieldou .call, nextrenvoie la valeur à la ligne où a yieldété appelé
MBO
la source
4
une pause dans un
processus lèvera
pouvez-vous citer où vous obtenez cette information à partir de ce "prochain retourne la valeur du bloc et termine l'appel". Je veux en savoir plus.
user566245
C'était du livre The Ruby Programming Language (je ne l'ai pas sous la main pour le moment) si je me souviens bien. Je viens de vérifier google et je crois que c'est de ce livre: librairie.immateriel.fr/fr/read_book/9780596516178/... et 2 page suivantex à partir de là (ce n'est pas mon contenu et mes pages, je viens de googler). Mais je recommande vraiment le livre original, il a beaucoup plus de gemmes expliquées.
MBO
J'ai aussi répondu de ma tête, ne vérifiant que les choses en irb, c'est pourquoi ma réponse n'est ni technique ni complète. Pour plus d'informations, consultez le livre Ruby Programming Language.
MBO
J'aurais aimé que cette réponse soit au sommet. Je ne peux pas assez le voter.
btx9000
20

Vous ne pouvez pas faire cela dans Ruby.

Le returnmot 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
# ...
molf
la source
Il y a un article détaillé sur le retour d'un bloc / proc ici
ComDubh
3

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
}
Simone Carletti
la source
1

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
giorgian
la source
1

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.

Myst
la source
1

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 yieldsi cela suffit dans votre scénario).

Cezary Baginski
la source
1

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 }
s12chung
la source