Cosa significa || = (o-uguale) in Ruby?


340

Cosa significa il seguente codice in Ruby?

||=

Ha qualche significato o ragione per la sintassi?

Risposte:


175

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.


2
Il secondo link ha sofferto di bit rot (commento da meta di stackoverflow.com/users/540162/nightfirecat ).
Andrew Grimm,

331
Questa è una non risposta molto criptica. La risposta breve sembra essere: a || = b significa, se a non è definito, assegnargli il valore di b, altrimenti lasciarlo da solo. (Ok, ci sono sfumature e casi speciali, ma questo è il caso base.)
Steve Bennett

20
@SteveBennett: non lo definirei il fatto che a = false; a ||= truenon non fare ciò che la tua risposta dice che fa una "sfumatura".
Jörg W Mittag,

23
Forse questa domanda è stata posta così tante volte perché le persone continuano a rispondere che questa domanda è stata posta così tante volte.
einnocent,

8
Con questa risposta è facile capire perché ci sono più thread. Se provi a cercare una risposta a questa domanda usando un cappello da principiante, noterai che tutte le risposte non sono chiare. Ad esempio, con questo stai solo dicendo ciò che non lo è. Suggerisco di migliorare la tua risposta e dare una risposta facile per i principianti: a = b a meno che a
Arnold Roa,

594

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 + b
  • a ||= 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:


52
Grazie per questa risposta, ha molto più senso.
Tom Hert,

non ha mai cercato abbastanza ma ancora non capisco perché dovresti usarlo al contrario di a = a || b. forse solo la mia opinione personale, ma un po 'ridicola che esista una tale sfumatura ...
dtc

2
@dtc, considera 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.)
antinome

1
Mi piace l'altra risposta per quanto approfondita, ma adoro questa risposta per la sua semplicità. Per qualcuno che impara Ruby, questo è il tipo di risposta di cui abbiamo bisogno. Se sapessimo cosa significava || =, probabilmente la domanda sarebbe stata formulata diversamente.
OBCENEIKON,

1
Cordiali saluti, 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 ...
Ajedi32,

32

Risposta concisa e completa

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.


1
Sei sicuro? Ciò implica che se aè falso / zero / non definito, viene valutato due volte. (Ma non conosco Ruby, quindi non so se i valori possono essere "valutati" esattamente ...)
Steve Bennett,

Capisco quello che stai dicendo. Quello che intendevo con due linee equivalenti è che lo stato finale sarà equivalente dopo che l'intera linea è stata valutata, ovvero il valore di a, b e ciò che viene restituito. Se gli interpreti rubini usano o meno stati diversi - come diverse valutazioni di a - per arrivarci è del tutto possibile. Qualche esperto di interprete rubino là fuori?
the_minted

3
Questo non è del tutto giusto. 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.
Ajedi32,

1
* correzione: a || a = bnon valuterà adue volte quando aè vero.
Ajedi32,

1
@the_minted 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.
Ajedi32

27

In breve, a||=bsignifica: Se aè undefined, nil or false, assegnare ba a. Altrimenti, mantieni aintatto.


16
Fondamentalmente,


x ||= y si intende

se xha qualche valore, lascialo in pace e non modifica il valore, altrimenti imposta xsuy


13

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.


8
Questo è sbagliato. Si prega di leggere Ruby-Forum.Com/topic/151660 e i collegamenti ivi forniti.
Jörg W Mittag,

1
@Jo (umlaut) rg, non vedo cosa c'è che non va. Il tuo link è un elenco di altri link. Nessuna vera spiegazione del perché sia ​​sbagliata, suona solo come un giudizio di valore da parte tua.
eggmatters

questa risposta è sbagliata, perché non solo si innesca undefined, ma anche su falsee nil, che potrebbe non essere pertinente per current_user, ma soprattutto falsepuò essere inatteso in altri casi
dfherr

Nonostante qualsiasi incompletezza che questa risposta possa mostrare (non funziona con zero / falso), è la prima che spiega perché vorresti usare || =, quindi grazie!
Jonathan Tuzman,


8

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 ||= ba || 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 ||= ba = 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 ||= ba = 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 ||= bdefined?(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.


Quindi il tuo terzo esempio esteso:(a=b unless a) or a
vol7ron,

1
@ vol7ron Che ha un problema simile al n. 2. Se 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.
Ajedi32,

Inoltre, prima frase, non dovrebbe dire assegnare baa , il rhs non assegna ancora ai lhs, o in altre parole, il lhs non imposta ancora il suo valore al rhs?
vol7ron,

La migliore a ||= brisposta che ho trovato su Internet. Grazie.
Eric Duminil,

3

unless x x = y end

a meno che x abbia un valore (non è nullo o falso), impostalo uguale a y

è equivalente a

x ||= y


3

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.


3

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.


2
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.


2

È come un'istanza pigra. Se la variabile è già definita, prenderà quel valore invece di creare nuovamente il valore.


2

Ricorda anche che ||=non è un'operazione atomica e quindi non è sicura per i thread. Come regola empirica, non utilizzarlo per i metodi di classe.


2

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


o nilo falsenon solonil
Alex Poca il

2

|| = è un operatore di assegnazione condizionale

  x ||= y

è equivalente a

  x = x || y

o in alternativa

if defined?(x) and x
    x = x
else 
    x = y
end

2

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

1

Come un malinteso comune, a ||= bnon equivale a a = a || b, ma si comporta come a || a = b.

Ma ecco che arriva un caso complicato. Se anon è definito, a || a = 42aumenta NameError, mentre a ||= 42restituisce 42. Quindi, non sembrano essere espressioni equivalenti.


1

||= assegna il valore a destra solo se left == nil (o è indefinito o falso).


probabilmente intendevi "assegna valore a sinistra" anziché a destra
Maysam Torabi,


0
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a ||= 2
=> 1

Perché aera già impostato su1

irb(main):003:0> a = nil
=> nil
irb(main):004:0> a ||= 2
=> 2

Perché lo aeranil


Qual è la data di risposta qui. Perché non sta mostrando l'anno?
Shiv,

0
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.


0

||= 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.


-2

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.

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.