2012-05-01 6 views
15

In una discussione sui loop di Ruby, è stato recentemente parlato di Niklas B. per il ciclo "non introduce un nuovo ambito", rispetto a ciascun ciclo. Mi piacerebbe vedere alcuni esempi di come si sente questo.Ruby per loop una trappola?

O.K., espongo la domanda: in quale altro punto di Ruby vediamo quali apri fanno/delimitano i blocchi di fine, ma in realtà non vi è alcuno scopo all'interno? Qualcos'altro oltre a ... do ... fine?

O.K., Un'ulteriore espansione della domanda, c'è un modo per scrivere per il ciclo con parentesi graffe {blocco}?

+0

Solo una nota a margine (ho già detto qualcosa del genere in un altro commento): il problema con 'for' non è solo che differisce in modo sottile dagli altri costrutti usando' do/end', ma anche che può essere usato solo per una sola ragione: attraversare un numero enumerabile. Spesso, i costrutti dalla programmazione funzionale come 'select' o' map' sono molto più adatti per esprimere un problema piuttosto che un semplice ciclo vecchio, ereditato direttamente da C. –

+0

E viceversa: dai esempi reali di dove pensi che sia necessario un ciclo 'for' e ti offriamo un'alternativa migliore? :) Altrimenti penso che questa domanda sia solo un duplicato di http://stackoverflow.com/questions/3294509/for-vs-each-in-ruby –

+1

E per quanto riguarda le prestazioni? Non è semplice per più veloce di entrare in una chiusura? E se dovessi _want_ definire le variabili locali in ciclo for per un uso successivo :)? (Solo pensieri rapidi su ciò che potrebbe essere buono :) –

risposta

14

Illustriamo il punto con un esempio:

results = [] 
(1..3).each do |i| 
    results << lambda { i } 
end 
p results.map(&:call) # => [1,2,3] 

fresco, questo è ciò che ci si aspettava. Ora verifica quanto segue:

results = [] 
for i in 1..3 
    results << lambda { i } 
end 
p results.map(&:call) # => [3,3,3] 

Huh, che sta succedendo? Credimi, questi tipi di bug sono cattivi da rintracciare. Gli sviluppatori Python o JS sapranno cosa intendo :)

Questo solo è un motivo per me per evitare questi cicli come la peste, anche se ci sono più buoni argomenti a favore di questa posizione. Come Ben ha sottolineato correttamente, l'utilizzo del metodo corretto da Enumerable porta quasi sempre a un codice migliore rispetto all'utilizzo di semplici vecchi, imperativi cicli for o fancier Enumerable#each. Ad esempio, l'esempio precedente potrebbe anche essere conciso scritto come

lambdas = 1.upto(3).map { |i| lambda { i } } 
p lambdas.map(&:call) 

espando la domanda: quale altro in Ruby vediamo cosa fare apears/blocco delimitatori finali, ma non c'è in realtà alcuna portata dentro? Qualcos'altro oltre a ... do ... fine?

Ogni singolo uno dei costrutti di loop può essere utilizzato in questo modo:

while true do 
    #... 
end 

until false do 
    # ... 
end 

D'altra parte, possiamo scrivere ogni uno di questi senza la do (che ovviamente è preferibile):

for i in 1..3 
end 

while true 
end 

until false 
end 

Un'altra espansione della domanda, c'è un modo per scrivere per ciclo con parentesi graffe {} blocco

No, non c'è. Si noti inoltre che il termine "blocco" ha un significato speciale in Ruby.

+3

Mi dilungherò anche su questo (risposta stupenda, Niklas B.). Mai dire mai, ma io * mai * scrivo e non voglio vedere cicli 'for' in codice Ruby. Non sono affatto idiomatiche e introducono cattivi trucchi per sviluppatori inconsapevoli. Usa invece qualcosa fornito da ['Enumerable'] (http://ruby-doc.org/core-1.9.3/Enumerable.html). –

+0

@ Ben: Certo, sono esattamente della tua opinione e l'ho chiarito nella discussione OP si riferisce a :) –

+0

Ah, vedo che hai! Grazie per averlo guidato a casa. –

1

Bene, anche i blocchi non sono perfetti in Ruby precedenti alla 1.9. Non sempre introducono nuovo ambito di applicazione:

i = 0 
results = [] 
(1..3).each do |i| 
    results << lambda { i } 
end 
i = 5 
p results.map(&:call) # => [5,5,5] 
+0

In Ruby 1.9, questo dovrebbe produrre '[1,2,3]', come previsto (inoltre, potrebbe attivare un avviso sulla variabile ombreggiata). –

+0

@NiklasB. Grazie, buono a sapersi, è un punto davvero debole di R1.8, tuttavia non ci sono avvertimenti in R1.9. Solo curioso di sapere come funziona con il codice legacy, ricordo che è stato un punto serio quando è stato discusso per 1.9 –

+0

Penso che avessero un avvertimento nelle prime versioni di 1.9 per rendere gli utenti del codice legacy consapevoli del problema. Sembra essere rimosso nelle versioni successive, però. –

2

In primo luogo, vi spiego il motivo per cui non si vuole utilizzare for, e poi spiegare perché si potrebbe.

Il motivo principale per cui non si desidera utilizzare for è che non è univoco. Se si utilizza each, è possibile sostituire facilmente lo each con uno map o uno find o uno each_with_index senza una modifica importante del codice. Ma non c'è for_map o for_find o for_with_index.

Un altro motivo è che se si crea una variabile all'interno di un blocco entro each e non è stata creata in precedenza, rimarrà in esistenza per tutto il tempo che il ciclo esiste. Liberarsi delle variabili una volta che non le usi, è una buona cosa.

Ora ti accennerò perché potresti voler usare for. each crea una chiusura per ogni ciclo e se ripeti quel ciclo troppe volte, quel loop può causare problemi di prestazioni. In https://stackoverflow.com/a/10325493/38765, ho pubblicato che utilizzando un ciclo while anziché un blocco reso più lento.

RUN_COUNT = 10_000_000 
FIRST_STRING = "Woooooha" 
SECOND_STRING = "Woooooha" 

def times_double_equal_sign 
    RUN_COUNT.times do |i| 
    FIRST_STRING == SECOND_STRING 
    end 
end 

def loop_double_equal_sign 
    i = 0 
    while i < RUN_COUNT 
    FIRST_STRING == SECOND_STRING 
    i += 1 
    end 
end 

times_double_equal_sign preso costantemente 2,4 secondi, mentre loop_double_equal_sign era costantemente 0,2 e 0,3 secondi più veloce.

In https://stackoverflow.com/a/6475413/38765, ho trovato che l'esecuzione di un ciclo vuoto richiedeva 1,9 secondi, mentre l'esecuzione di un blocco vuoto richiedeva 5,7 secondi.

Sapere perché non si desidera utilizzare for, sapere perché si desidera utilizzare for e utilizzare solo quest'ultimo quando è necessario. A meno che non provi nostalgia per altre lingue. :)