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 anon è 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 ||= truenon 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 be impostare ail risultato . Allo stesso modo, se adefinito e valutato in modo veritiero, allora bnon 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 + ba ||= b si traduce approssimativamente in a || a = bÈ una scorciatoia per a || a = b. La differenza è che, quando anon definito, a || a = baumenterebbe NameError, mentre si a ||= bimposta asu b. Questa distinzione non è importante se ae bsono 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] || 2vs h[1] || h[1] = 2. Entrambe le espressioni valutano 0ma 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 = bgenera un NameErrorif se anon definito. a ||= bnon lo fa, ma invece lo inizializza ae lo imposta su b. Questa è l'unica distinzione tra i due per quanto ne so. Allo stesso modo, l'unica differenza tra a = a || be di a ||= bcui sono a conoscenza è che se a=è un metodo, verrà chiamato indipendentemente da ciò che aritorna. Inoltre, l'unica differenza tra a = b unless ae di a ||= bcui sono consapevole è che quell'affermazione valuta nilinvece che ase 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 endgetterà un errore se anon è definito, mentre a ||= be a = a || bnon 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 endvalutare adue volte quando aè truthy, mentre a ||= be a = a || bnon lo fanno.
a || a = bnon valuterà adue volte quando aè vero.
the end state will be equivalent after the whole line has been evaluatedQuesto non è necessariamente vero però. E se afosse 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 ||= brestituirà 6, ma self.a ? self.a : self.a = brestituirà 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 falsee nil, che potrebbe non essere pertinente per current_user, ma soprattutto falsepuò essere inatteso in altri casi
x ||= y
è
x || x = y
"se x è falso o indefinito, allora x punta a y"
Per essere precisi, a ||= bsignifica "se aè indefinito o falso ( falseo nil), impostato asu be valuta su (cioè ritorno) b, altrimenti valuta su a".
Altri spesso cercano di illustrarlo dicendo che a ||= bequivale a a || a = bo 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 atratta di una variabile locale non definita. In tal caso, a ||= bverrà impostato asu b(e valutato su b), mentre a || a = baumenterà 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 ||= bnon farà nulla (oltre a valutare a a), mentre a = a || bchiamerà a=(a)il aricevitore. Come altri hanno sottolineato, questo può fare la differenza quando la chiamata a=aha 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 avaluterà nil(anche se non asarà ancora impostato, come previsto), mentre a ||= bvaluterà a.
a ||= b⇔defined?(a) ? (a || a = b) : (a = b) ????
Ancora no Queste affermazioni possono differire quando method_missingesiste un metodo che restituisce un valore di verità a. In questo caso, a ||= bvaluterà a qualunque method_missingrendimenti, e non tentare di serie a, mentre defined?(a) ? (a || a = b) : (a = b)fisserà aper be 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 ||= bsia 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 ||= bnon è equivalente a a || a = bquando aè una variabile locale non definita? Bene, a = nil if falseassicura che anon 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, aimpiega molto tempo a tornare o ha effetti collaterali.
baa , il rhs non assegna ancora ai lhs, o in altre parole, il lhs non imposta ancora il suo valore al rhs?
a ||= brisposta che ho trovato su Internet. Grazie.
Supponiamo a = 2cheb = 3
POI, a ||= b si tradurrà nel avalore del ie 2.
Come quando un valore di valutazione non risulta falseo no nil. Ecco perché llnon valuta bil valore.
Ora supponiamo a = nile b = 3.
Quindi si a ||= botterrà il valore di 3ie b.
Come prima prova a valutare il valore di a che ha portato a nil... così ha valutato bil 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_usernon è 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
nilo falsenon solonil
Se XNON 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 bvalore non verrà assegnato a a. aavrà 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 @usersvariabile 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=nilprima di ogni incarico.