Perché i punti esclamativi vengono utilizzati nei metodi Ruby?


540

In Ruby alcuni metodi hanno un punto interrogativo ( ?) che pone una domanda come include?quella se viene incluso l'oggetto in questione, questo restituisce un vero / falso.

Ma perché alcuni metodi hanno punti esclamativi ( !) dove altri no?

Cosa significa?


21
sinonimo: botto, punto esclamativo
prusswan

17
La risposta accettata deve essere modificata in stackoverflow.com/a/612653/109618 . Vedi wobblini.net/bang.txt e ruby-forum.com/topic/176830#773946 - "Il segno del botto significa" che la versione del botto è più pericolosa della sua controparte non del botto; maneggiare con cura "" -Matz
David J.

2
Il metodo del botto sarebbe un'ottima scelta progettuale se solo e tutti i metodi del botto fossero pericolosi. Purtroppo non lo sono, e quindi diventa un esercizio frustrante nel memorizzare ciò che è e non è mutabile.
Damien Roche,

Risposte:


617

In generale, i metodi che finiscono !indicano che il metodo modificherà l'oggetto su cui è chiamato . Ruby li chiama " metodi pericolosi " perché cambiano stato a cui qualcun altro potrebbe fare riferimento. Ecco un semplice esempio per le stringhe:

foo = "A STRING"  # a string called foo
foo.downcase!     # modifies foo itself
puts foo          # prints modified foo

Questo produrrà:

a string

Nelle librerie standard, ci sono molti posti in cui vedrai coppie di metodi con nomi simili, uno con il !e uno senza. Quelli senza sono chiamati "metodi sicuri", e restituiscono una copia dell'originale con le modifiche applicate alla copia , con la chiamata invariata. Ecco lo stesso esempio senza !:

foo = "A STRING"    # a string called foo
bar = foo.downcase  # doesn't modify foo; returns a modified string
puts foo            # prints unchanged foo
puts bar            # prints newly created bar

Questo produce:

A STRING
a string

Tieni presente che questa è solo una convenzione, ma molte classi di Ruby la seguono. Ti aiuta anche a tenere traccia di ciò che viene modificato nel tuo codice.


2
Ci sono anche casi come exit versus exit! e (in rotaie) salva contro salva!
Andrew Grimm,

24
Fai molta attenzione: molte librerie più piccole non seguono questa convenzione. Se accadono cose strane, spesso sostituendo obj.whatever! con obj = obj.whatever! lo risolve. Molto frustrante.
Sarah Mei,

101
bang è anche usato per i metodi che sollevano un'eccezione quando il metodo no, ad esempio: savee save!inActiveRecord
ecoologic

3
@AbhilashAK save! genera un errore se non è possibile salvare. Questo è contrario al normale salvataggio che restituisce vero / falso.
BookOfGreg

31
@tgamblin Ci sono molti metodi in Ruby che mutano senza botto. Ci sono anche metodi rari che non mutano con il botto ma fanno qualcosa di sorprendente come aumentare gli errori o saltare gli errori. I botti sono usati per dire che questa è la versione più insolita del metodo e penso che questo dovrebbe riflettersi nella tua risposta poiché è contrassegnato come corretto.
BookOfGreg

143

Il punto esclamativo significa molte cose, e talvolta non si può dire molto da esso se non "questo è pericoloso, stai attento".

Come altri hanno già detto, nei metodi standard viene spesso usato per indicare un metodo che fa mutare un oggetto, ma non sempre. Si noti che molti metodi standard cambiare il loro ricevitore e non hanno un punto esclamativo ( pop, shift, clear), e alcuni metodi con punti esclamativi non cambiare il loro ricevitore ( exit!). Vedi questo articolo per esempio.

Altre librerie potrebbero usarlo in modo diverso. In Rails un punto esclamativo spesso significa che il metodo genererà un'eccezione in caso di fallimento piuttosto che in modo silenzioso.

È una convenzione di denominazione ma molte persone la usano in modi leggermente diversi. Nel tuo codice una buona regola è quella di usarlo ogni volta che un metodo sta facendo qualcosa di "pericoloso", specialmente quando esistono due metodi con lo stesso nome e uno di essi è più "pericoloso" dell'altro. "Pericoloso" può significare quasi tutto però.


75

Questa convenzione di denominazione è revocata da Scheme .

1.3.5 Convenzioni di denominazione

Per convenzione, i nomi delle procedure che restituiscono sempre un valore booleano di solito terminano con ``? ''. Tali procedure sono chiamate predicati.

Per convenzione, i nomi delle procedure che memorizzano i valori in posizioni precedentemente allocate (vedere la sezione 3.4) di solito terminano con ``! ''. Tali procedure sono chiamate procedure di mutazione. Per convenzione, il valore restituito da una procedura di mutazione non è specificato.


2
+1 a questa risposta dal momento che ha una documentazione che fornisce spiegazioni ragionevoli per il! utilizzo. Davvero una buona risposta Steven
David Silveira il

Grazie @DavidSilveira!
Steven Huwig,

24

! in genere significa che il metodo agisce sull'oggetto anziché restituire un risultato. Dal libro Programmazione di Ruby :

I metodi che sono "pericolosi" o che modificano il ricevitore potrebbero essere nominati con un "!" Finale.


18

È più preciso dire che i metodi con un botto! sono la versione più pericolosa o sorprendente . Esistono molti metodi che mutano senza un botto come .destroye in generale i metodi hanno solo botti in cui esiste una alternativa più sicura nella libreria centrale.

Ad esempio, su Array abbiamo .compacte .compact!, entrambi i metodi mutano l'array, ma.compact! restituisce zero invece di sé se non ci sono zero nell'array, il che è più sorprendente del semplice ritorno di sé.

Il metodo unico non-mutanti che ho trovato con il botto è Kernels' .exit!che è più sorprendente .exitperché non si può prendere SystemExitmentre il processo si sta chiudendo.

Rails e ActiveRecord continuano questa tendenza in quanto utilizza il botto per effetti più "sorprendenti" come quelli .create!che generano errori in caso di fallimento.


16

Da themomorohoax.com:

Un botto può essere utilizzato nei modi seguenti, in base alle mie preferenze personali.

1) Un metodo di registrazione attivo genera un errore se il metodo non fa ciò che dice che farà.

2) Un metodo di registrazione attivo salva il record o un metodo salva un oggetto (ad es. Striscia!)

3) Un metodo fa qualcosa di "extra", come post in qualche posto, o fa qualche azione.

Il punto è: usa il botto solo quando hai davvero pensato se è necessario, per evitare ad altri sviluppatori il fastidio di dover controllare perché stai usando il botto.

Il botto fornisce due spunti ad altri sviluppatori.

1) che non è necessario salvare l'oggetto dopo aver chiamato il metodo

2) quando si chiama il metodo, il db verrà modificato.

http://www.themomorohoax.com/2009/02/11/when-to-use-a-bang-exclamation-point-after-rails-methods


6

Semplice spiegazione:

foo = "BEST DAY EVER" #assign a string to variable foo.

=> foo.downcase #call method downcase, this is without any exclamation.

"best day ever"  #returns the result in downcase, but no change in value of foo.

=> foo #call the variable foo now.

"BEST DAY EVER" #variable is unchanged.

=> foo.downcase! #call destructive version.

=> foo #call the variable foo now.

"best day ever" #variable has been mutated in place.

Ma se avessi mai chiamato un metodo downcase!nella spiegazione sopra, foocambierebbe in downcase in modo permanente. downcase!non restituirebbe un nuovo oggetto stringa ma sostituirebbe la stringa in posizione, cambiando totalmente fooin downcase. Ti suggerisco di non usare a downcase!meno che non sia totalmente necessario.


1
!

Mi piace pensare a questo come a un cambiamento esplosivo che distrugge tutto ciò che è accaduto prima. Bang o punto esclamativo significa che stai apportando una modifica permanente salvata nel tuo codice.

Se ad esempio usi il metodo di Ruby per la sostituzione globalegsub! la sostituzione che fai è permanente.

Un altro modo in cui puoi immaginarlo è aprire un file di testo e trovare e sostituire, seguito dal salvataggio. !fa lo stesso nel tuo codice.

Un altro utile promemoria se vieni dal mondo bash è sed -iquesto effetto simile di apportare modifiche permanenti salvate.


1

Chiamati "metodi distruttivi" Tendono a cambiare la copia originale dell'oggetto a cui ti riferisci.

numbers=[1,0,10,5,8]
numbers.collect{|n| puts n*2} # would multiply each number by two
numbers #returns the same original copy
numbers.collect!{|n| puts n*2} # would multiply each number by two and destructs the original copy from the array
numbers   # returns [nil,nil,nil,nil,nil]

0

In conclusione: i !metodi cambiano semplicemente il valore dell'oggetto su cui sono chiamati, mentre un metodo senza! restituisce un valore manipolato senza scrivere sull'oggetto su cui è stato chiamato il metodo.

Utilizzare solo !se non si prevede di aver bisogno del valore originale memorizzato nella variabile su cui è stato chiamato il metodo.

Preferisco fare qualcosa del tipo:

foo = "word"
bar = foo.capitalize
puts bar

O

foo = "word"
puts foo.capitalize

Invece di

foo = "word"
foo.capitalize!
puts foo

Nel caso in cui vorrei accedere nuovamente al valore originale.


1
Perché la tua risposta non è stata utile in alcun modo. "In conclusione: i metodi cambiano semplicemente il valore dell'oggetto su cui sono chiamati" non è vero.
Darwin

@Darwin si fa cambiare il valore dell'oggetto. !muta l'oggetto anziché restituire una copia modificata.
Charles

Quindi cosa pensi che faccia? User.create!
Darwin,

@Darwin in quale contesto? ActiveRecord?
Charles,

Sì, ActiveRecord.
Darwin,
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.