Utilizzo di do block vs parentesi graffe {}


112

Nuovo a Ruby, indossa i tuoi guanti da principiante.

C'è qualche differenza (oscura o pratica) tra i seguenti due snippet?

my_array = [:uno, :dos, :tres]
my_array.each { |item| 
    puts item
}

my_array = [:uno, :dos, :tres]
my_array.each do |item| 
    puts item
end

Mi rendo conto che la sintassi delle parentesi graffe ti consentirebbe di posizionare il blocco su una riga

my_array.each { |item| puts item }

ma al di fuori di questo ci sono ragioni valide per usare una sintassi rispetto all'altra?


5
Ottima domanda. Sono anche interessato a ciò che preferiscono i rubini esperti.
Jonathan Sterling




1
Puoi anche scrivere una riga del blocco do sebbene sia veramente utile solo quando fai qualcosa come eval ("my_array.each do | item |; put item; end") ma funziona in irb o pry senza eval e virgolette Tu può imbattersi in una situazione in cui è preferibile. Non chiedermi quando però. Questo è un altro argomento su cui indagare.
Douglas G. Allen

Risposte:


101

Il libro di cucina di Ruby dice che la sintassi delle parentesi ha un ordine di precedenza maggiore rispetto ado..end

Tieni presente che la sintassi delle parentesi ha una precedenza maggiore rispetto alla sintassi do..end. Considera i seguenti due frammenti di codice:

1.upto 3 do |x|
  puts x
end

1.upto 3 { |x| puts x }
# SyntaxError: compile error

Il secondo esempio funziona solo quando si usano le parentesi, 1.upto(3) { |x| puts x }


8
Ah, capito. Quindi, a causa dell'ordine di precedenza, quando usi do, stai passando il blocco come parametro aggiuntivo, ma quando usi le parentesi stai passando il blocco come primo parametro dei risultati delle invocazioni del metodo a la sinistra.
Alan Storm

2
Spesso preferisco risposte brevi, ma la risposta di bkdir è molto più chiara.
yakout

71

Questa è una domanda un po 'vecchia ma vorrei provare a spiegare un po' di più su {}edo .. end

come si dice prima

la sintassi delle parentesi ha un ordine di precedenza maggiore rispetto a do..end

ma come questo fa la differenza:

method1 method2 do
  puts "hi"
end

in questo caso, il metodo1 verrà chiamato con il blocco di do..ende il metodo2 verrà passato al metodo1 come argomento! che è equivalente amethod1(method2){ puts "hi" }

ma se dici

method1 method2{
  puts "hi"
}

quindi il metodo2 verrà chiamato con il blocco, quindi il valore restituito verrà passato al metodo1 come argomento. Che è equivalente amethod1(method2 do puts "hi" end)

def method1(var)
    puts "inside method1"
    puts "method1 arg = #{var}"
    if block_given?
        puts "Block passed to method1"
        yield "method1 block is running"
    else
        puts "No block passed to method1"
    end
end

def method2
    puts"inside method2"
    if block_given?
        puts "Block passed to method2"
        return yield("method2 block is running")
    else
        puts "no block passed to method2"
        return "method2 returned without block"
    end
end

#### test ####

method1 method2 do 
    |x| puts x
end

method1 method2{ 
    |x| puts x
}

#### produzione ####

#inside method2
#no block passed to method2
#inside method1
#method1 arg = method2 returned without block
#Block passed to method1
#method1 block is running

#inside method2
#Block passed to method2
#method2 block is running
#inside method1
#method1 arg = 
#No block passed to method1

39

In genere, la convenzione è quella di utilizzare {}quando si esegue una piccola operazione, ad esempio una chiamata a un metodo o un confronto, ecc., Quindi questo ha perfettamente senso:

some_collection.each { |element| puts element }

Ma se hai una logica leggermente complessa che va su più righe, usa do .. endcome:

1.upto(10) do |x|
  add_some_num = x + rand(10)
  puts '*' * add_some_num
end

Fondamentalmente, si tratta di, se la logica del blocco va su più righe e non può essere adattata sulla stessa riga, utilizzare do .. ende se la logica del blocco è semplice e solo una semplice / singola riga di codice, utilizzare {}.


6
Sono d'accordo con l'uso di do / end per blocchi multilinea, ma userò le parentesi graffe se concateno metodi aggiuntivi alla fine del blocco. Stilisticamente mi piace {...}. Method (). Method () rispetto a do ... end.method (). Method, ma potrei essere solo io.
Tin Man,

1
D'accordo, anche se preferisco assegnare il risultato di un metodo con blocco a una variabile significativa e quindi chiamare un altro metodo su di esso come result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodse ciò lo rendesse un po 'più facilmente comprensibile. Ma in genere eviterei un disastro ferroviario.
nas

Mi chiedo perché questo stile sia così popolare. C'è una ragione diversa da "L'abbiamo sempre fatto in questo modo"?
iGEL

9

Esistono due stili comuni per la scelta dei blocchi do endrispetto a quelli { }per i blocchi in Ruby:

Il primo e molto comune stile è stato reso popolare da Ruby on Rails e si basa su una semplice regola di linea singola e multi-linea:

  • Usa le parentesi graffe { }per i blocchi a riga singola
  • Utilizzare do endper blocchi multilinea

Questo ha senso perché do / end legge male in una riga singola, ma per i blocchi multi-riga, lasciare una chiusura }appesa su una propria riga non è coerente con tutto il resto che usa endin ruby, come le definizioni di modulo, classe e metodo ( defecc. .) e strutture di controllo ( if, while, case, etc.)

Il secondo stile, meno visto di frequente, è noto come semantico, o " parentesi graffe di Weirich ", proposto dal compianto, grande rubyist Jim Weirich:

  • Utilizzare do endper blocchi procedurali
  • Usa le parentesi graffe { }per i blocchi funzionali

Ciò significa che quando il blocco viene valutato per il suo valore di ritorno , dovrebbe essere {}concatenabile e le parentesi graffe hanno più senso per il concatenamento dei metodi.

D'altra parte, quando il blocco viene valutato per i suoi effetti collaterali , il valore restituito non ha conseguenze e il blocco sta semplicemente "facendo" qualcosa, quindi non ha senso essere concatenato.

Questa distinzione nella sintassi trasmette un significato visivo sulla valutazione del blocco e se è necessario preoccuparsi o meno del suo valore di ritorno.

Ad esempio, qui il valore di ritorno del blocco viene applicato a ogni elemento:

items.map { |i| i.upcase }

Tuttavia, qui non viene utilizzato il valore di ritorno del blocco. Funziona in modo procedurale e produce un effetto collaterale:

items.each do |item|
  puts item
end

Un altro vantaggio dello stile semantico è che non è necessario cambiare le parentesi graffe per fare / terminare solo perché è stata aggiunta una linea al blocco.

Come osservazione, casualmente i blocchi funzionali sono spesso una riga singola e i blocchi procedurali (ad esempio config) sono multilinea. Quindi, seguire lo stile Weirich finisce per sembrare quasi uguale allo stile Rails.


1

Ho usato lo stile Weirich per anni, ma mi sono allontanato da questo per usare sempre le parentesi graffe . Non ricordo di aver mai usato le informazioni dallo stile del blocco e la definizione è un po 'vaga. Per esempio:

date = Timecop.freeze(1.year.ago) { format_date(Time.now) }
customer = Timecop.freeze(1.year.ago) { create(:customer) }

Questi sono procuduali o funzionali?

E la cosa del conteggio delle linee è inutile secondo me. Lo so, se ci sono 1 o più linee, e perché dovrei cambiare esattamente lo stile solo perché ho aggiunto o rimosso linee?

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.