Co oznacza || = (lub-równa się) w Ruby?


340

Co oznacza następujący kod w Ruby?

||=

Czy ma to jakieś znaczenie lub przyczynę dla składni?

Odpowiedzi:


175

To pytanie było omawiane tak często na listach mailingowych Ruby i blogach Ruby, że teraz są nawet wątki na liście mailingowej Ruby, których jedynym celem jest zebranie linków do wszystkich innych wątków na liście mailingowej Ruby, które omawiają ten problem .

Oto jeden: Ostateczna lista wątków i stron || = (OR Equal)

Jeśli naprawdę chcesz wiedzieć, co się dzieje, zapoznaj się z sekcją 11.4.2.3 „Skrócone zadania” specyfikacji wersji językowej Ruby .

Jako pierwsze przybliżenie

a ||= b

jest równa

a || a = b

i nie równoważne z

a = a || b

Jest to jednak tylko pierwsze przybliżenie, zwłaszcza jeśli ajest niezdefiniowane. Semantyka różni się również w zależności od tego, czy jest to proste przypisanie zmiennej, przypisanie metody czy przypisanie indeksowania:

a    ||= b
a.c  ||= b
a[c] ||= b

wszyscy są traktowani inaczej.


2
Drugi link ucierpiał z powodu zgnilizny bitów (komentarz meta autorstwa stackoverflow.com/users/540162/nightfirecat ).
Andrew Grimm,

331
To bardzo tajemnicza brak odpowiedzi. Krótka odpowiedź wydaje się brzmieć: a || = b oznacza, że ​​jeśli a jest niezdefiniowane, to przypisz mu wartość b, w przeciwnym razie zostaw to w spokoju. (Okej, są niuanse i specjalne przypadki, ale to jest podstawowy przypadek.)
Steve Bennett

20
@SteveBennett: Nie nazwałbym fakt, że a = false; a ||= truema nie robić to, co twoja odpowiedź mówi, że robi „niuans”.
Jörg W Mittag

23
Może to pytanie zostało zadane tyle razy, ponieważ ludzie wciąż odpowiadają, że pytanie to zadawano tyle razy.
niewinny

8
Dzięki tej odpowiedzi łatwo zrozumieć, dlaczego istnieje wiele wątków. Jeśli spróbujesz znaleźć odpowiedź na to pytanie za pomocą czapki dla początkujących, zauważysz, że wszystkie odpowiedzi nie są jasne. Na przykład dzięki temu mówisz po prostu to, czego nie ma. Sugeruję poprawić odpowiedź i udzielić łatwych odpowiedzi dla nowicjuszy: a = b, chyba że a
Arnold Roa

594

a ||= bjest operatorem przypisania warunkowego . Oznacza to, że jeśli ajest niezdefiniowany lub falsey , to oceń bi ustaw awynik . Odpowiednio, jeśli ajest zdefiniowane i ocenia zgodnie z prawdą, bto nie jest oceniane i nie ma miejsca przypisanie. Na przykład:

a ||= nil # => nil
a ||= 0 # => 0
a ||= 2 # => 0

foo = false # => false
foo ||= true # => true
foo ||= false # => true

Myląco wygląda podobnie do innych operatorów przypisania (takich jak +=), ale zachowuje się inaczej.

  • a += b przetłumaczyć na a = a + b
  • a ||= b z grubsza przekłada się na a || a = b

Jest to prawie skrót dla a || a = b. Różnica polega na tym, że gdy ajest niezdefiniowany, a || a = bpodnosi się NameError, a a ||= bustawia ana b. To rozróżnienie nie jest ważne, jeśli ai boba są zmiennymi lokalnymi, ale jest znaczące, jeśli jest metodą getter / setter klasy.

Dalsza lektura:


52
Dziękuję za tę odpowiedź, ma ona znacznie większy sens.
Tom Hert

nie szukałem wystarczająco dużo, ale wciąż nie rozumiem, dlaczego miałbyś użyć tego w przeciwieństwie do a = a || b. może tylko moje osobiste zdanie, ale trochę śmieszne, że taki niuans istnieje ...
dtc

2
@dtc, rozważ h = Hash.new(0); h[1] ||= 2. Rozważmy teraz dwa możliwe rozszerzenia h[1] = h[1] || 2vs h[1] || h[1] = 2. Oba wyrażenia oceniają na, 0ale pierwsze niepotrzebnie zwiększa rozmiar skrótu. Być może dlatego Matz postanowił sprawić, by ||=zachowywał się bardziej jak drugie rozszerzenie. (
Oparłem

1
Podoba mi się inna odpowiedź na to, jak dogłębnie to idzie, ale uwielbiam tę odpowiedź ze względu na jej prostotę. Dla kogoś, kto uczy się Ruby, potrzebujemy tego rodzaju odpowiedzi. Gdybyśmy wiedzieli, co oznacza || =, to pytanie prawdopodobnie byłoby sformułowane inaczej.
OBCENEIKON,

1
Fyi, a || a = bpodnosi NameErrorif ajest niezdefiniowane. a ||= bnie, ale zamiast tego inicjuje ai ustawia na b. To jedyne rozróżnienie między tymi dwoma, o ile mi wiadomo. Podobnie, jedyną różnicą między a = a || bi a ||= b, o której jestem świadomy, jest to, że jeśli a=metoda jest wywoływana, niezależnie od tego, co azwróci. Poza tym jedyną różnicą między mną, a = b unless aa a ||= btym, o czym jestem świadomy, jest to, że to stwierdzenie sprawdza, czy nilzamiast jest prawdziwe. Wiele przybliżeń, ale nic zupełnie równoważnego ...aa
Ajedi32,

32

Zwięzła i pełna odpowiedź

a ||= b

ocenia w taki sam sposób, jak każdy z poniższych wierszy

a || a = b
a ? a : a = b
if a then a else a = b end

-

Z drugiej strony,

a = a || b

ocenia w taki sam sposób, jak każdy z poniższych wierszy

a = a ? a : b
if a then a = a else a = b end

-

Edycja: Jak zauważył AJedi32 w komentarzach, jest to prawdą tylko wtedy, gdy: 1. a jest zmienną zdefiniowaną. 2. Ocena raz i dwa razy nie powoduje różnicy w stanie programu lub systemu.


1
Czy jesteś pewien? Oznacza to, że jeśli afalse = zero / undefined, to jest oceniane dwukrotnie. (Ale nie znam Ruby, więc nie wiem, czy wartości można dokładnie „ocenić”)
Steve Bennett,

Rozumiem co mówisz. Mam na myśli to, że dwie linie są równoważne, że stan końcowy będzie równoważny po ocenie całej linii, co oznacza wartość a, b i co jest zwracane. To, czy tłumacze Ruby używają różnych stanów - jak kilka ocen a - aby się tam dostać, jest całkowicie możliwe. Są jacyś eksperci od tłumacza ruby?
the_minted

3
To nie do końca prawda. a || a = b, a ? a : a = b, if a then a else a = b end, I if a then a = a else a = b endwygeneruje błąd, jeśli ajest niezdefiniowana, natomiast a ||= bi a = a || bnie będzie. Ponadto a || a = b, a ? a : a = b, if a then a else a = b end, a = a ? a : b, i if a then a = a else a = b endocenić adwukrotnie gdy ajest truthy, natomiast a ||= bi a = a || bnie.
Ajedi32,

1
* korekta: a || a = bnie będzie oceniać adwukrotnie, kiedy ajest to prawda.
Ajedi32,

1
@ themmeded the end state will be equivalent after the whole line has been evaluatedTo niekoniecznie prawda. Co jeśli ajest metodą? Metody mogą mieć skutki uboczne. Np public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5, self.a ||= bpowróci 6, ale self.a ? self.a : self.a = bpowróci 7.
Ajedi32

27

W skrócie a||=boznacza: Jeśli atak undefined, nil or false, przypisz bdo a. W przeciwnym razie zachowaj anienaruszony.


16
Gruntownie,


x ||= y znaczy

jeśli xma jakąkolwiek wartość, zostaw ją w spokoju i nie zmieniaj wartości, w przeciwnym razie ustaw xnay


13

Oznacza or-równa się. Sprawdza, czy wartość po lewej jest zdefiniowana, a następnie użyj jej. Jeśli nie, użyj wartości po prawej stronie. Możesz go użyć w Railsach do buforowania zmiennych instancji w modelach.

Szybki przykład oparty na Railsach, w którym tworzymy funkcję pobierania aktualnie zalogowanego użytkownika:

class User > ActiveRecord::Base

  def current_user
    @current_user ||= User.find_by_id(session[:user_id])
  end

end

Sprawdza, czy ustawiona jest zmienna instancji @current_user. Jeśli tak, zwróci go, zapisując w ten sposób wywołanie bazy danych. Jeśli jednak nie jest ustawiony, wykonujemy wywołanie, a następnie ustawiamy na to zmienną @current_user. Jest to bardzo prosta technika buforowania, ale doskonale nadaje się do wielokrotnego pobierania tej samej zmiennej instancji w aplikacji.


8
To jest źle. Proszę przeczytać Ruby-Forum.Com/topic/151660 i linki tam zawarte.
Jörg W Mittag

1
@Jo (umlaut) rg, nie widzę, co w tym złego. Twój link to lista innych linków. Nie ma prawdziwego wyjaśnienia, dlaczego jest źle, po prostu brzmi jak osąd wartości po twojej stronie.
eggmatters

ta odpowiedź jest błędna, ponieważ nie tylko włącza się undefined, ale także włącza falsei nil, co może nie mieć znaczenia current_user, ale szczególnie falsemoże być nieoczekiwana w innych przypadkach
dfherr

Pomimo wszelkiej niekompletności, jaką ta odpowiedź może wykazywać (nie działa dla zera / fałszu), to pierwsza wyjaśnia, dlaczego chcesz użyć || =, więc dziękuję!
Jonathan Tuzman,


8

Mówiąc dokładniej, a ||= boznacza „jeśli ajest niezdefiniowany lub fałszywy ( falselub nil), ustaw ana bi oceń na (tj. Zwróć ) b, w przeciwnym razie oceń na a”.

Inni często próbują to zilustrować, mówiąc, że a ||= bjest to równoważne z a || a = blub a = a || b. Te równoważności mogą być pomocne w zrozumieniu pojęcia, ale należy pamiętać, że nie są one dokładne we wszystkich warunkach. Pozwól mi wyjaśnić:

  • a ||= ba || a = b ?

    Zachowanie tych instrukcji różni się, gdy ajest niezdefiniowaną zmienną lokalną. W takim przypadku a ||= bustawi się ana b(i ocenia na b), podczas gdy a || a = bpodniesie NameError: undefined local variable or method 'a' for main:Object.

  • a ||= ba = a || b ?

    Równoważność z tych stwierdzeń są często zakłada się, gdyż podobna równoważność jest prawdziwe dla innych skrótem przypisania operatorów (czyli +=, -=, *=, /=, %=, **=, &=, |=, ^=, <<=, i >>=). Jednak ||=zachowanie tych instrukcji może się różnić, gdy a=jest metodą na obiekcie i ajest prawdziwa. W takim przypadku a ||= bnie zrobi nic (prócz oceny a), a a = a || bzadzwoni a=(a)do aodbiornika. Jak zauważyli inni , może to mieć znaczenie, gdy wywoływanie a=ama skutki uboczne, takie jak dodawanie kluczy do skrótu.

  • a ||= ba = b unless a ??

    Zachowanie tych stwierdzeń różni się tylko tym, co oceniają, kiedy ajest zgodne z prawdą. W takim przypadku a = b unless aoceni na nil(choć anadal nie zostanie ustawiony zgodnie z oczekiwaniami), podczas gdy a ||= boceni na a.

  • a ||= bdefined?(a) ? (a || a = b) : (a = b) ????

    Nadal nie. Te instrukcje mogą się różnić, gdy method_missingistnieje metoda, która zwraca prawdziwą wartość a. W takim przypadku a ||= boceni do jakichkolwiek method_missingzwrotów, a nie spróbuje ustawić a, natomiast defined?(a) ? (a || a = b) : (a = b)ustawi ana bi oceni do b.

Okej, okej, więc co jest a ||= b równoważne? Czy istnieje sposób na wyrażenie tego w Ruby?

Zakładając, że niczego nie przeoczę , uważam, że a ||= bjest funkcjonalnie równoważny ... ( bęben )

begin
  a = nil if false
  a || a = b
end

Czekaj! Czy to nie tylko pierwszy przykład z noopem przed nim? Cóż, niezupełnie. Pamiętasz, jak powiedziałem wcześniej, że nie a ||= bjest to równoważne tylko a || a = bwtedy, gdy anieokreślona zmienna lokalna? Cóż, a = nil if falsezapewnia, że anigdy nie jest niezdefiniowany, nawet jeśli ta linia nigdy nie jest wykonywana. Zmienne lokalne w Ruby mają zasięg leksykalny.


Zatem twój rozszerzony trzeci przykład:(a=b unless a) or a
vol7ron

1
@ vol7ron Ma podobny problem jak # 2. Jeśli ajest metodą, zostanie wywołana dwa razy zamiast raz (jeśli za pierwszym razem zwróci prawdziwą wartość). Może to powodować różne zachowania, jeśli na przykład apowrót zajmuje dużo czasu lub ma skutki uboczne.
Ajedi32,

Ponadto, pierwsze zdanie, czy nie powinno to oznaczać przypisać bdoa , czy rhs wciąż przypisuje do lhs, czy innymi słowy, czy lhs nadal nie ustawia swojej wartości na rhs?
vol7ron

Najlepsza a ||= bodpowiedź, jaką znalazłem w Internecie. Dzięki.
Eric Duminil

3

unless x x = y end

chyba że x ma wartość (nie jest zero ani fałsz), ustaw ją na wartość y

jest równa

x ||= y


3

Przypuszczać a = 2 ib = 3

TO a ||= b zostanie obliczone na awartość tj2 .

Tak jak wtedy, gdy ewaluacja do jakiejś wartości nie była wynikiem falselub nil… Dlatego llnie jest ocenianab wartości.

Teraz Załóżmy a = nilib = 3 .

Wtedy a ||= bbędzie wynikał 3tjb wartości „s.

Gdy najpierw próbuje oszacować wartość, która spowodowała nil... więc to oszacowałb wartość.

Najlepszym przykładem zastosowanym w aplikacji ror jest:

#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

Gdzie User.find_by_id(session[:user_id])jest odpalany wtedy i tylko wtedy, gdy @current_usernie został wcześniej zainicjowany.


3

a || = b

Oznacza, że ​​jakakolwiek wartość jest obecna w „a” i nie chcesz jej zmieniać, zachowaj tę wartość, w przeciwnym razie, jeśli „a” nie ma żadnej wartości, użyj wartości „b”.

Proste słowa, jeśli lewa strona, jeśli nie jest pusta, wskazują na istniejącą wartość, w przeciwnym razie wskazują na wartość po prawej stronie.


2
a ||= b

jest równa

a || a = b

i nie

a = a || b

z powodu sytuacji, w której zdefiniujesz skrót z wartością domyślną (skrót zwróci wartość domyślną dla wszelkich niezdefiniowanych kluczy)

a = Hash.new(true) #Which is: {}

Jeśli użyjesz:

a[10] ||= 10 #same as a[10] || a[10] = 10

a jest nadal:

{}

ale kiedy piszesz tak:

a[10] = a[10] || 10

a staje się:

{10 => true}

ponieważ przypisałeś sobie wartość klucza 10, która domyślnie ma wartość true, więc hash jest teraz definiowany dla klucza 10, zamiast nigdy nie wykonywać przypisania w pierwszej kolejności.


2

To jak leniwa instancja. Jeśli zmienna jest już zdefiniowana, pobierze tę wartość zamiast ponownie ją tworzyć.


2

Pamiętaj również, że ||=nie jest to operacja atomowa, a zatem nie jest bezpieczna dla wątków. Zasadniczo nie używaj go do metod klasowych.


2

To jest domyślna notacja przydziału

na przykład: x || = 1
sprawdzi, czy x jest zerowe, czy nie. Jeśli x rzeczywiście wynosi zero, wówczas przypisuje mu tę nową wartość (1 w naszym przykładzie)

dokładniej:
jeśli x == zero
x = 1
koniec


albo nilalbo falsenie tylkonil
Alex Poca,

2

|| = jest operatorem przypisania warunkowego

  x ||= y

jest równa

  x = x || y

lub alternatywnie

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

2

Jeśli XNIE ma wartości, zostanie jej przypisana wartość Y. W przeciwnym razie zachowa oryginalną wartość, 5 w tym przykładzie:

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

Jako powszechne nieporozumienie a ||= bnie jest równoważne a = a || b, ale zachowuje się jak a || a = b.

Ale nadchodzi trudna sprawa. Jeśli anie jest zdefiniowany, a || a = 42podnosi NameError, a a ||= 42wraca 42. Nie wydają się więc równoważnymi wyrażeniami.


1

||= przypisuje wartość do prawej tylko wtedy, gdy left == zero (lub jest niezdefiniowany lub fałszywy).


prawdopodobnie miałeś na myśli „przypisuje wartość do lewej” zamiast do prawej
Maysam Torabi,


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

Ponieważ azostał już ustawiony na1

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

Ponieważ abyłnil


Jaka jest tutaj data odpowiedzi? Dlaczego nie pokazuje roku?
Shiv

0
b = 5
a ||= b

To przekłada się na:

a = a || b

który będzie

a = nil || 5

więc w końcu

a = 5

Teraz, jeśli zadzwonisz ponownie:

a ||= b
a = a || b
a = 5 || 5
a = 5

b = 6

Teraz, jeśli zadzwonisz ponownie:

a ||= b
a = a || b
a = 5 || 6
a = 5 

Jeśli zaobserwujesz, bwartość nie zostanie przypisana a. anadal będzie miał 5.

Jest to wzorzec zapamiętywania używany w Ruby w celu przyspieszenia dostępu.

def users
  @users ||= User.all
end

To w zasadzie przekłada się na:

@users = @users || User.all

Dlatego po raz pierwszy wywołasz tę metodę, wykonasz połączenie z bazą danych.

Przyszłe wywołania tej metody zwrócą tylko wartość @userszmiennej instancji.


0

||= nazywa się operatorem przypisania warunkowego.

Zasadniczo działa, =ale z wyjątkiem tego, że jeśli zmienna została już przypisana , nic nie zrobi.

Pierwszy przykład:

x ||= 10

Drugi przykład:

x = 20
x ||= 10

W pierwszym przykładzie xjest teraz równy 10. Jednak w drugim przykładzie xjest już zdefiniowany jako 20. Zatem operator warunkowy nie ma wpływu. xma jeszcze 20 po uruchomieniu x ||= 10.


-2

a ||= bto to samo co powiedzenie a = b if a.nil?luba = b unless a

Ale czy wszystkie 3 opcje wykazują taką samą wydajność? Z Ruby 2.5.1 to

1000000.times do
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
  a ||= 1
end

na moim komputerze trwa 0,099 sekundy

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

zajmuje 0,062 sekundy. To prawie 40% szybciej.

a następnie mamy również:

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

co zajmuje 0,166 sekundy.

Nie znaczy to, że ogólnie będzie to miało znaczący wpływ na wydajność, ale jeśli potrzebujesz ostatniej optymalizacji, rozważ ten wynik. Tak poza tym:a = 1 unless a jest łatwiejszy do odczytania dla nowicjusza, jest oczywisty.

Uwaga 1: powodem wielokrotnego powtarzania linii przypisania jest zmniejszenie narzutu pętli w mierzonym czasie.

Uwaga 2: Wyniki są podobne, jeśli zrobię a=nilzero przed każdym zadaniem.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.