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.
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éVous ne pouvez pas faire ça en Ruby.
Le mot clé return
always 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
# ...
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
}
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).
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
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.
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 }