Come uscire da un blocco di rubini?


420

Ecco qui Bar#do_things:

class Bar   
  def do_things
    Foo.some_method(x) do |x|
      y = x.do_something
      return y_is_bad if y.bad? # how do i tell it to stop and return do_things? 
      y.do_something_else
    end
    keep_doing_more_things
  end
end

Ed ecco qui Foo#some_method:

class Foo
  def self.some_method(targets, &block)
    targets.each do |target|
      begin
        r = yield(target)
      rescue 
        failed << target
      end
    end
  end
end

Ho pensato di usare il rilancio, ma sto cercando di renderlo generico, quindi non voglio aggiungere nulla di specifico Foo.

Risposte:


747

Usa la parola chiave next. Se non si desidera continuare con l'elemento successivo, utilizzare break.

Quando nextviene utilizzato all'interno di un blocco, fa sì che il blocco esca immediatamente, restituendo il controllo al metodo iteratore, che può quindi iniziare una nuova iterazione invocando nuovamente il blocco:

f.each do |line|              # Iterate over the lines in file f
  next if line[0,1] == "#"    # If this line is a comment, go to the next
  puts eval(line)
end

Se utilizzato in un blocco, breaktrasferisce il controllo fuori dal blocco, dall'iteratore che ha invocato il blocco e alla prima espressione che segue l'invocazione dell'iteratore:

f.each do |line|             # Iterate over the lines in file f
  break if line == "quit\n"  # If this break statement is executed...
  puts eval(line)
end
puts "Good bye"              # ...then control is transferred here

E infine, l'uso di returnin un blocco:

return fa sempre in modo che il metodo racchiuso ritorni, indipendentemente da quanto sia profondamente annidato all'interno dei blocchi (tranne nel caso di lambda):

def find(array, target)
  array.each_with_index do |element,index|
    return index if (element == target)  # return from find
  end
  nil  # If we didn't find the element, return nil
end

2
grazie, ma il prossimo passa solo all'elemento successivo nella matrice. è possibile uscire?
user169930

Devi chiamare il prossimo con il valore di ritorno. "def f; x = yield; mette x; end" "f fa il prossimo 3; mette" o "; end" Questo stampa 3 (ma nessuna "o") sulla console.
Marcel Jackwerth,

5
next, break, return, Non si può confrontare
finiteloop

Ho aggiunto una risposta espandendo il commento @MarcelJackwerth aggiunto sull'uso nexto breakcon un argomento.
Tyler Holien,

8
C'è anche una parola chiave chiamata redo, che in pratica riporta semplicemente l'esecuzione all'inizio del blocco all'interno dell'iterazione corrente.
Ajedi32,

59

Volevo solo essere in grado di uscire da un blocco, una specie di goto in avanti, non molto legato a un loop. In effetti, voglio interrompere un blocco che si trova in un loop senza terminare il loop. Per fare ciò, ho reso il blocco un ciclo di una iterazione:

for b in 1..2 do
    puts b
    begin
        puts 'want this to run'
        break
        puts 'but not this'
    end while false
    puts 'also want this to run'
end

Spero che questo aiuti il ​​prossimo googler che atterra qui in base alla riga dell'oggetto.


4
Questa è stata l'unica risposta che ha risposto alla domanda mentre veniva posta. Merita più punti. Grazie.
Alex Nye,

Funziona allo stesso modo sia per l'interruzione che per la successiva. Se il falso viene cambiato in vero, il prossimo rimarrà nello sguardo e scoppierà.
G. Allen Morris III,

39

Se si desidera che il blocco per restituire un valore utile (ad esempio quando si usa #map, #injecte così via), nexte breakanche accettare un argomento.

Considera quanto segue:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    if x % 3 == 0
      count + 2
    elsif x.odd?
      count + 1
    else 
      count
    end
  end
end

L'equivalente usando next:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    next count if x.even?
    next (count + 2) if x % 3 == 0
    count + 1
  end
end

Ovviamente, puoi sempre estrarre la logica necessaria in un metodo e chiamarla dall'interno del tuo blocco:

def contrived_example(numbers)
  numbers.inject(0) { |count, x| count + extracted_logic(x) }
end

def extracted_logic(x)
  return 0 if x.even?
  return 2 if x % 3 == 0
  1
end

1
Grazie per il suggerimento con l'argomento per break!
rkallensee,

2
potresti aggiornare con un esempio specifico usando break(probabilmente sostituisci uno dei tuoi nextcon break..
Mike Graf

Una cosa molto interessante. break somethingfunziona, break(something)funziona ma true && break(somehting)genera un errore di sintassi. Cordiali saluti. Se è necessaria la condizione, allora ifo unlessdeve essere utilizzata.
Akostadinov,


8

Forse puoi usare i metodi integrati per trovare elementi particolari in una matrice, invece di each-ing targetse fare tutto a mano. Alcuni esempi:

class Array
  def first_frog
    detect {|i| i =~ /frog/ }
  end

  def last_frog
    select {|i| i =~ /frog/ }.last
  end
end

p ["dog", "cat", "godzilla", "dogfrog", "woot", "catfrog"].first_frog
# => "dogfrog"
p ["hats", "coats"].first_frog
# => nil
p ["houses", "frogcars", "bottles", "superfrogs"].last_frog
# => "superfrogs"

Un esempio potrebbe essere fare qualcosa del genere:

class Bar
  def do_things
    Foo.some_method(x) do |i|
      # only valid `targets` here, yay.
    end
  end
end

class Foo
  def self.failed
    @failed ||= []
  end

  def self.some_method(targets, &block)
    targets.reject {|t| t.do_something.bad? }.each(&block)
  end
end

2
Non aggiungere metodi arbitrari come quello alla classe Array! È davvero una brutta pratica.
mrbrdo,

3
Rails lo fa, quindi perché non può?
wberry,

2
@wberry Questo non vuol dire che dovrebbe necessariamente . ;) In generale, tuttavia, è meglio evitare le classi core di patching delle scimmie a meno che non si abbia una ragione abbastanza buona (vale a dire l'aggiunta di alcune funzionalità generalizzabili molto utili che molti altri codici troveranno utili). Anche allora, cammina leggermente perché una volta che una classe è pesantemente patchata dalle scimmie, è facile per le biblioteche iniziare a camminare l'una sull'altra e causare un comportamento estremamente strano.
blm768,

2

nexte breaksembra fare la cosa giusta in questo esempio semplificato!

class Bar
  def self.do_things
      Foo.some_method(1..10) do |x|
            next if x == 2
            break if x == 9
            print "#{x} "
      end
  end
end

class Foo
    def self.some_method(targets, &block)
      targets.each do |target|
        begin
          r = yield(target)
        rescue  => x
          puts "rescue #{x}"
        end
     end
   end
end

Bar.do_things

uscita: 1 3 4 5 6 7 8


2
l'interruzione termina immediatamente - il successivo continua alla successiva iterazione.
Ben Aubin,

-3

Per uscire da un blocco rubino basta usare la returnparola chiave return if value.nil?


2
Non returnesce dalla funzione?
ragerdl,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.