Cosa significa il seguente codice in Ruby?
||=
Ha qualche significato o ragione per la sintassi?
Cosa significa il seguente codice in Ruby?
||=
Ha qualche significato o ragione per la sintassi?
Risposte:
Questa domanda è stata discussa così spesso nelle mailing list di Ruby e nei blog di Ruby che ora ci sono persino thread nella mailing list di Ruby il cui unico scopo è quello di raccogliere collegamenti a tutti gli altri thread della mailing list di Ruby che trattano questo problema .
Eccone uno: l'elenco definitivo di thread e pagine || = (OR uguale)
Se vuoi davvero sapere cosa sta succedendo, dai un'occhiata alla Sezione 11.4.2.3 "Compiti abbreviati" della specifica del progetto di lingua Ruby .
Come prima approssimazione,
a ||= b
è equivalente a
a || a = b
e non equivalente a
a = a || b
Tuttavia, questa è solo una prima approssimazione, soprattutto se a
non è definita. La semantica differisce anche a seconda che si tratti di una semplice assegnazione variabile, di una assegnazione di metodo o di una indicizzazione:
a ||= b
a.c ||= b
a[c] ||= b
sono tutti trattati in modo diverso.
a = false; a ||= true
non non fare ciò che la tua risposta dice che fa una "sfumatura".
a ||= b
è un operatore di assegnazione condizionale . Significa se a
è indefinito o falso , quindi valutare b
e impostare a
il risultato . Allo stesso modo, se a
definito e valutato in modo veritiero, allora b
non viene valutato e non ha luogo alcun incarico. Per esempio:
a ||= nil # => nil
a ||= 0 # => 0
a ||= 2 # => 0
foo = false # => false
foo ||= true # => true
foo ||= false # => true
Confusamente, sembra simile ad altri operatori di assegnazione (come +=
), ma si comporta diversamente.
a += b
si traduce in a = a + b
a ||= b
si traduce approssimativamente in a || a = b
È una scorciatoia per a || a = b
. La differenza è che, quando a
non definito, a || a = b
aumenterebbe NameError
, mentre si a ||= b
imposta a
su b
. Questa distinzione non è importante se a
e b
sono entrambe variabili locali, ma è significativa se uno dei due è un metodo getter / setter di una classe.
Ulteriori letture:
h = Hash.new(0); h[1] ||= 2
. Consideriamo ora le due possibili espansioni h[1] = h[1] || 2
vs h[1] || h[1] = 2
. Entrambe le espressioni valutano 0
ma la prima aumenta inutilmente la dimensione dell'hash. Forse è per questo che Matz ha scelto di ||=
comportarsi in modo più simile alla seconda espansione. (Ho basato questo su un esempio di uno dei thread collegati in un'altra risposta.)
a || a = b
genera un NameError
if se a
non definito. a ||= b
non lo fa, ma invece lo inizializza a
e lo imposta su b
. Questa è l'unica distinzione tra i due per quanto ne so. Allo stesso modo, l'unica differenza tra a = a || b
e di a ||= b
cui sono a conoscenza è che se a=
è un metodo, verrà chiamato indipendentemente da ciò che a
ritorna. Inoltre, l'unica differenza tra a = b unless a
e di a ||= b
cui sono consapevole è che quell'affermazione valuta nil
invece che a
se a
è vera. Molte approssimazioni, ma niente del tutto equivalente ...
a ||= b
valuta allo stesso modo di ciascuna delle seguenti righe
a || a = b
a ? a : a = b
if a then a else a = b end
-
D'altro canto,
a = a || b
valuta allo stesso modo di ciascuna delle seguenti righe
a = a ? a : b
if a then a = a else a = b end
-
Modifica: Come ha sottolineato AJedi32 nei commenti, questo vale solo se: 1. a è una variabile definita. 2. La valutazione di una volta e due volte non determina una differenza nello stato del programma o del sistema.
a
è falso / zero / non definito, viene valutato due volte. (Ma non conosco Ruby, quindi non so se i valori possono essere "valutati" esattamente ...)
a || a = b
, a ? a : a = b
, if a then a else a = b end
, E if a then a = a else a = b end
getterà un errore se a
non è definito, mentre a ||= b
e a = a || b
non sarà. Inoltre, a || a = b
, a ? a : a = b
, if a then a else a = b end
, a = a ? a : b
, e if a then a = a else a = b end
valutare a
due volte quando a
è truthy, mentre a ||= b
e a = a || b
non lo fanno.
a || a = b
non valuterà a
due volte quando a
è vero.
the end state will be equivalent after the whole line has been evaluated
Questo non è necessariamente vero però. E se a
fosse un metodo? I metodi possono avere effetti collaterali. Ad esempio public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5
, self.a ||= b
restituirà 6, ma self.a ? self.a : self.a = b
restituirà 7.
Significa o-uguale a. Verifica se il valore a sinistra è definito, quindi utilizzalo. In caso contrario, utilizzare il valore a destra. Puoi usarlo in Rails per memorizzare nella cache le variabili di istanza nei modelli.
Un rapido esempio basato su Rails, in cui creiamo una funzione per recuperare l'utente attualmente connesso:
class User > ActiveRecord::Base
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
end
Verifica se la variabile di istanza @current_user è impostata. Se lo è, lo restituirà, salvando così una chiamata al database. Se non è impostato, effettuiamo la chiamata e quindi impostiamo la variabile @current_user su quella. È una tecnica di memorizzazione nella cache molto semplice ma è ideale per quando si recupera più volte la stessa variabile di istanza nell'applicazione.
undefined
, ma anche su false
e nil
, che potrebbe non essere pertinente per current_user
, ma soprattutto false
può essere inatteso in altri casi
x ||= y
è
x || x = y
"se x è falso o indefinito, allora x punta a y"
Per essere precisi, a ||= b
significa "se a
è indefinito o falso ( false
o nil
), impostato a
su b
e valuta su (cioè ritorno) b
, altrimenti valuta su a
".
Altri spesso cercano di illustrarlo dicendo che a ||= b
equivale a a || a = b
o a = a || b
. Queste equivalenze possono essere utili per la comprensione del concetto, ma essere consapevoli che essi sono non accurati in tutte le condizioni. Mi permetta di spiegare:
a ||= b
⇔a || a = b
?
Il comportamento di queste istruzioni differisce quando si a
tratta di una variabile locale non definita. In tal caso, a ||= b
verrà impostato a
su b
(e valutato su b
), mentre a || a = b
aumenterà NameError: undefined local variable or method 'a' for main:Object
.
a ||= b
⇔a = a || b
?
L'equivalenza di queste affermazioni sono spesso presume, poiché un'equivalenza simile vale per altri assegnazione abbreviata operatori ( +=
, -=
, *=
, /=
, %=
, **=
, &=
, |=
, ^=
, <<=
, e >>=
). Tuttavia, per ||=
il comportamento di queste affermazioni può essere diverso quando a=
è un metodo su un oggetto ed a
è vero. In tal caso, a ||= b
non farà nulla (oltre a valutare a a
), mentre a = a || b
chiamerà a=(a)
il a
ricevitore. Come altri hanno sottolineato, questo può fare la differenza quando la chiamata a=a
ha effetti collaterali, come l'aggiunta di chiavi a un hash.
a ||= b
⇔a = b unless a
??
Il comportamento di queste affermazioni differisce solo in ciò a cui valutano quando a
è vero. In tal caso, a = b unless a
valuterà nil
(anche se non a
sarà ancora impostato, come previsto), mentre a ||= b
valuterà a
.
a ||= b
⇔defined?(a) ? (a || a = b) : (a = b)
????
Ancora no Queste affermazioni possono differire quando method_missing
esiste un metodo che restituisce un valore di verità a
. In questo caso, a ||= b
valuterà a qualunque method_missing
rendimenti, e non tentare di serie a
, mentre defined?(a) ? (a || a = b) : (a = b)
fisserà a
per b
e valutare a b
.
Ok, ok, così quello che è a ||= b
equivalente a? C'è un modo per esprimerlo in Ruby?
Bene, supponendo che non stia trascurando nulla, credo che a ||= b
sia funzionalmente equivalente a ... ( rullo di tamburi )
begin
a = nil if false
a || a = b
end
Resisti! Non è solo il primo esempio con un noop prima? Bene, non proprio. Ricordi come ho detto prima che a ||= b
non è equivalente a a || a = b
quando a
è una variabile locale non definita? Bene, a = nil if false
assicura che a
non sia mai definito, anche se quella linea non viene mai eseguita. Le variabili locali in Ruby hanno un ambito lessicale.
(a=b unless a) or a
a
è un metodo, verrà chiamato due volte anziché una volta (se restituisce un valore di verità la prima volta). Ciò potrebbe causare comportamenti diversi se, ad esempio, a
impiega molto tempo a tornare o ha effetti collaterali.
b
aa
, il rhs non assegna ancora ai lhs, o in altre parole, il lhs non imposta ancora il suo valore al rhs?
a ||= b
risposta che ho trovato su Internet. Grazie.
Supponiamo a = 2
cheb = 3
POI, a ||= b
si tradurrà nel a
valore del ie 2
.
Come quando un valore di valutazione non risulta false
o no nil
. Ecco perché ll
non valuta b
il valore.
Ora supponiamo a = nil
e b = 3
.
Quindi si a ||= b
otterrà il valore di 3
ie b
.
Come prima prova a valutare il valore di a che ha portato a nil
... così ha valutato b
il valore.
Il miglior esempio utilizzato nell'app ror è:
#To get currently logged in iser
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
# Make current_user available in templates as a helper
helper_method :current_user
Dove, User.find_by_id(session[:user_id])
viene attivato se e solo se @current_user
non è stato inizializzato prima.
a || = b
Indica se è presente un valore in "a" e non si desidera modificarlo per continuare a utilizzare quel valore, altrimenti se "a" non ha alcun valore, utilizzare il valore di "b".
Le parole semplici, se a sinistra se non null, indicano un valore esistente, altrimenti indicano un valore a destra.
a ||= b
è equivalente a
a || a = b
e non
a = a || b
a causa della situazione in cui si definisce un hash con un valore predefinito (l'hash restituirà il valore predefinito per eventuali chiavi non definite)
a = Hash.new(true) #Which is: {}
se usi:
a[10] ||= 10 #same as a[10] || a[10] = 10
a è ancora:
{}
ma quando lo scrivi in questo modo:
a[10] = a[10] || 10
a diventa:
{10 => true}
perché hai assegnato il valore di se stesso alla chiave 10
, che per impostazione predefinita è vero, quindi ora l'hash è definito per la chiave 10
, piuttosto che non eseguire mai l'assegnazione in primo luogo.
Ricorda anche che ||=
non è un'operazione atomica e quindi non è sicura per i thread. Come regola empirica, non utilizzarlo per i metodi di classe.
Questa è la notazione di assegnazione predefinita
ad esempio: x || = 1
verificherà se x è zero o no. Se x è effettivamente nullo, gli verrà quindi assegnato quel nuovo valore (1 nel nostro esempio)
più esplicito:
se x == nil
x = 1
fine
nil
o false
non solonil
Se X
NON ha un valore, verrà assegnato il valore di Y
. Altrimenti, manterrà il suo valore originale, 5 in questo esempio:
irb(main):020:0> x = 5
=> 5
irb(main):021:0> y = 10
=> 10
irb(main):022:0> x ||= y
=> 5
# Now set x to nil.
irb(main):025:0> x = nil
=> nil
irb(main):026:0> x ||= y
=> 10
||=
assegna il valore a destra solo se left == nil (o è indefinito o falso).
Questa sintassi ruby-lang. La risposta corretta è controllare la documentazione di ruby-lang. Tutte le altre spiegazioni sono offuscate .
"ruby-lang docs Abbreviated Assignment".
https://docs.ruby-lang.org/en/2.4.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment
b = 5
a ||= b
Questo si traduce in:
a = a || b
che sarà
a = nil || 5
così finalmente
a = 5
Ora se lo chiami di nuovo:
a ||= b
a = a || b
a = 5 || 5
a = 5
b = 6
Ora se lo chiami di nuovo:
a ||= b
a = a || b
a = 5 || 6
a = 5
Se si osserva, il b
valore non verrà assegnato a a
. a
avrà ancora 5
.
È un modello di memorizzazione che viene utilizzato in Ruby per accelerare gli accessori.
def users
@users ||= User.all
end
Questo in pratica si traduce in:
@users = @users || User.all
Quindi effettuerai una chiamata al database per la prima volta che chiami questo metodo.
Le chiamate future a questo metodo restituiranno semplicemente il valore della @users
variabile di istanza.
||=
viene chiamato operatore di assegnazione condizionale.
Funziona sostanzialmente come, =
ma con l'eccezione che se una variabile è già stata assegnata non farà nulla.
Primo esempio:
x ||= 10
Secondo esempio:
x = 20
x ||= 10
Nel primo esempio x
è ora uguale a 10. Tuttavia, nel secondo esempio x
è già definito come 20. Quindi l'operatore condizionale non ha alcun effetto. x
è ancora 20 dopo l'esecuzione x ||= 10
.
a ||= b
è come dire a = b if a.nil?
oa = b unless a
Ma tutte e 3 le opzioni mostrano le stesse prestazioni? Con Ruby 2.5.1 questo
1000000.times do
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
end
impiega 0,099 secondi sul mio PC, mentre
1000000.times do
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
a = 1 unless a
end
impiega 0,062 secondi. È quasi il 40% più veloce.
e poi abbiamo anche:
1000000.times do
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
a = 1 if a.nil?
end
che richiede 0,166 secondi.
Non che questo abbia un impatto significativo sulle prestazioni in generale, ma se hai bisogno dell'ultimo bit di ottimizzazione, considera questo risultato. A proposito: a = 1 unless a
è più facile da leggere per il principiante, è autoesplicativo.
Nota 1: motivo per ripetere più volte la riga di assegnazione è ridurre l'overhead del loop sul tempo misurato.
Nota 2: I risultati sono simili se lo faccio a=nil
prima di ogni incarico.