Verwenden von 'return' in einem Ruby-Block

86

Ich versuche, Ruby 1.9.1 für eine eingebettete Skriptsprache zu verwenden, damit "Endbenutzer" -Code in einen Ruby-Block geschrieben wird. Ein Problem dabei ist, dass ich möchte, dass die Benutzer das Schlüsselwort 'return' in den Blöcken verwenden können, damit sie sich nicht um implizite Rückgabewerte kümmern müssen. In diesem Sinne möchte ich Folgendes tun können:

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

thing {
  return 6 * 7
}

Wenn ich im obigen Beispiel 'return' verwende, erhalte ich einen LocalJumpError. Mir ist bewusst, dass dies daran liegt, dass der fragliche Block ein Proc und kein Lambda ist. Der Code funktioniert, wenn ich 'return' entferne, aber ich würde es wirklich vorziehen, in diesem Szenario 'return' verwenden zu können. Ist das möglich? Ich habe versucht, den Block in ein Lambda umzuwandeln, aber das Ergebnis ist das gleiche.

MetaFu
quelle
Warum möchten Sie einen impliziten Rückgabewert vermeiden?
Marcgg
@marcgg - Ich habe hier eine verwandte Frage - stackoverflow.com/questions/25953519/… .
Sid Smith

Antworten:

170

Verwenden Sie einfach nextin diesem Zusammenhang:

$ 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 kehrt immer von der Methode zurück, aber wenn Sie dieses Snippet in irb testen, haben Sie keine Methode, deshalb haben Sie LocalJumpError
  • breakGibt den Wert vom Block zurück und beendet den Aufruf. Wenn Ihr Block von yieldoder aufgerufen wurde .call, wird auch breakdieser Iterator unterbrochen
  • nextGibt den Wert vom Block zurück und beendet den Aufruf. Wenn Ihr Block von yieldoder aufgerufen wurde .call, wird nextder Wert an die Zeile zurückgegeben, in yieldder er aufgerufen wurde
MBO
quelle
4
Pause in einem Proc wird eine Ausnahme
auslösen
Können Sie angeben, woher Sie diese Informationen beziehen? "next gibt den Wert vom Block zurück und beendet den Aufruf". Ich möchte mehr darüber lesen.
user566245
Es war aus dem Ruby Programming Language- Buch (ich habe es momentan nicht zur Hand), wenn ich mich richtig erinnere. Ich habe gerade Google überprüft und ich glaube, es stammt aus diesem Buch: librairie.immateriel.fr/fr/read_book/9780596516178/… und 2 nächste pagex von dort (es ist nicht mein Inhalt und meine Seiten, ich habe es nur gegoogelt). Aber ich empfehle wirklich das Originalbuch, es hat viel mehr Edelsteine ​​erklärt.
MBO
Außerdem antwortete ich von meinem Kopf aus und überprüfte nur die Dinge in irb. Deshalb ist meine Antwort nicht technisch oder vollständig. Weitere Informationen finden Sie im Ruby Programming Language-Buch.
MBO
Ich wünschte, diese Antwort wäre ganz oben. Ich kann es nicht genug bewerten.
BTX9000
20

In Ruby ist das nicht möglich.

Das returnSchlüsselwort wird im aktuellen Kontext immer von der Methode oder dem Lambda zurückgegeben. In Blöcken wird von der Methode zurückgekehrt, in der der Abschluss definiert wurde . Es kann nicht veranlasst werden, von der aufrufenden Methode oder dem Lambda zurückzukehren.

Die Rubyspec zeigt, dass dies tatsächlich das richtige Verhalten für Ruby ist (zwar keine echte Implementierung, aber volle Kompatibilität mit 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
quelle
Es gibt einen ausführlichen Artikel über die Rückkehr von einem Block /
Proc
3

Sie sehen es aus der falschen Sicht. Dies ist eine Ausgabe von thing, nicht das Lambda.

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

thing {
  6 * 7
}
Simone Carletti
quelle
1

Wo wird etwas aufgerufen? Bist du in einer Klasse?

Sie können Folgendes in Betracht ziehen:

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
quelle
1

Ich hatte das gleiche Problem beim Schreiben eines DSL für ein Webframework in Ruby ... (das Webframework Anorexic wird rocken!) ...

Trotzdem habe ich mich in die Rubin-Interna vertieft und mit dem LocalJumpError, der zurückgegeben wurde, wenn ein Proc return zurückgibt, eine einfache Lösung gefunden.

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

Die if-Anweisung im Rettungssegment könnte wahrscheinlich ungefähr so ​​aussehen:

if e.is_a? LocalJumpError

Aber für mich ist es Neuland, also bleibe ich bei dem, was ich bisher getestet habe.

Myst
quelle
1

Ich glaube, dies ist trotz der Nachteile die richtige Antwort:

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
}

Dieser Hack ermöglicht es Benutzern, die Rückgabe in ihren Prozessen ohne Konsequenzen zu verwenden, das Selbst bleibt erhalten usw.

Der Vorteil der Verwendung von Thread besteht darin, dass Sie in einigen Fällen den LocalJumpError nicht erhalten - und die Rückgabe an der unerwartetsten Stelle erfolgt (neben einer Methode der obersten Ebene, bei der der Rest des Körpers unerwartet übersprungen wird).

Der Hauptnachteil ist der potenzielle Overhead (Sie können den Thread + Join durch den ersetzen, yieldwenn dies in Ihrem Szenario ausreicht).

Cezary Baginski
quelle
1

Ich habe einen Weg gefunden, aber es geht darum, eine Methode als Zwischenschritt zu definieren:

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

thing { return 6 * 7 }
s12chung
quelle