web-dev-qa-db-fra.com

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, de sorte qu'ils n'aient pas à se soucier 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 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.

78
MetaFu

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 retourne toujours de méthode, mais si vous testez cet extrait dans irb, vous n'avez pas de méthode, c'est pourquoi vous avez LocalJumpError
  • break renvoie la valeur du bloc et termine son appel. Si votre bloc a été appelé par yield ou .call, alors break rompt également cet itérateur.
  • next renvoie la valeur du bloc et termine son appel. Si votre bloc a été appelé par yield ou .call, alors next renvoie la valeur à la ligne où yield a été appelé
160
MBO

Vous ne pouvez pas faire ça en Ruby.

Le mot clé returnalways est renvoyé de la méthode ou de la méthode lambda dans le contexte actuel. Dans les blocs, il reviendra de la méthode dans laquelle la fermeture était définie. Il est impossible de revenir de la méthode calling ou de 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
# ...
16
molf

Vous le regardez du mauvais point de vue ... C'est un problème de thing, pas le lambda.

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

thing {
  6 * 7
}
3
Simone Carletti

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 le retour dans leurs processus sans conséquences, si l’auto est préservé, etc.

L'avantage d'utiliser Thread ici est que, dans certains cas, vous n'obtiendrez pas l'erreur LocalJumpError - et le retour aura lieu à l'endroit le plus inattendu (à côté d'une méthode de niveau supérieur, ignorant inopinément le reste de son corps). 

Le principal inconvénient est le surcoût potentiel (vous pouvez remplacer le thread + rejoindre par seulement le yield si cela est suffisant dans votre scénario).

1
Cezary Baginski

Où est la chose 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
1
giorgian

J'ai eu le même problème en écrivant un DSL pour un framework web en Ruby ... (le framework web Anorexic va basculer!) ...

quoi qu’il en soit, j’ai plongé dans les composants internes de Ruby et trouvé une solution simple à l’aide de LocalJumpError renvoyé lorsqu’un appel Proc revient, il fonctionne bien dans les tests jusqu’à présent, mais je ne suis pas sûr que ce soit tout à fait sûr:

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.

0
Myst

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 }
0
s12chung