Co oznacza następujący kod w Ruby?
||=
Czy ma to jakieś znaczenie lub przyczynę dla składni?
Co oznacza następujący kod w Ruby?
||=
Czy ma to jakieś znaczenie lub przyczynę dla składni?
Odpowiedzi:
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 a
jest 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.
a = false; a ||= true
ma nie robić to, co twoja odpowiedź mówi, że robi „niuans”.
a ||= b
jest operatorem przypisania warunkowego . Oznacza to, że jeśli a
jest niezdefiniowany lub falsey , to oceń b
i ustaw a
wynik . Odpowiednio, jeśli a
jest zdefiniowane i ocenia zgodnie z prawdą, b
to 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 a
jest niezdefiniowany, a || a = b
podnosi się NameError
, a a ||= b
ustawia a
na b
. To rozróżnienie nie jest ważne, jeśli a
i b
oba są zmiennymi lokalnymi, ale jest znaczące, jeśli jest metodą getter / setter klasy.
Dalsza lektura:
h = Hash.new(0); h[1] ||= 2
. Rozważmy teraz dwa możliwe rozszerzenia h[1] = h[1] || 2
vs h[1] || h[1] = 2
. Oba wyrażenia oceniają na, 0
ale pierwsze niepotrzebnie zwiększa rozmiar skrótu. Być może dlatego Matz postanowił sprawić, by ||=
zachowywał się bardziej jak drugie rozszerzenie. (
a || a = b
podnosi NameError
if a
jest niezdefiniowane. a ||= b
nie, ale zamiast tego inicjuje a
i ustawia na b
. To jedyne rozróżnienie między tymi dwoma, o ile mi wiadomo. Podobnie, jedyną różnicą między a = a || b
i a ||= b
, o której jestem świadomy, jest to, że jeśli a=
metoda jest wywoływana, niezależnie od tego, co a
zwróci. Poza tym jedyną różnicą między mną, a = b unless a
a a ||= b
tym, o czym jestem świadomy, jest to, że to stwierdzenie sprawdza, czy nil
zamiast jest prawdziwe. Wiele przybliżeń, ale nic zupełnie równoważnego ...a
a
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.
a
false = zero / undefined, to jest oceniane dwukrotnie. (Ale nie znam Ruby, więc nie wiem, czy wartości można dokładnie „ocenić”)
a || a = b
, a ? a : a = b
, if a then a else a = b end
, I if a then a = a else a = b end
wygeneruje błąd, jeśli a
jest niezdefiniowana, natomiast a ||= b
i a = a || b
nie 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 end
ocenić a
dwukrotnie gdy a
jest truthy, natomiast a ||= b
i a = a || b
nie.
a || a = b
nie będzie oceniać a
dwukrotnie, kiedy a
jest to prawda.
the end state will be equivalent after the whole line has been evaluated
To niekoniecznie prawda. Co jeśli a
jest metodą? Metody mogą mieć skutki uboczne. Np public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5
, self.a ||= b
powróci 6, ale self.a ? self.a : self.a = b
powróci 7.
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.
undefined
, ale także włącza false
i nil
, co może nie mieć znaczenia current_user
, ale szczególnie false
może być nieoczekiwana w innych przypadkach
x ||= y
jest
x || x = y
„jeśli x jest fałszywe lub niezdefiniowane, to x wskazuje na y”
Mówiąc dokładniej, a ||= b
oznacza „jeśli a
jest niezdefiniowany lub fałszywy ( false
lub nil
), ustaw a
na b
i oceń na (tj. Zwróć ) b
, w przeciwnym razie oceń na a
”.
Inni często próbują to zilustrować, mówiąc, że a ||= b
jest to równoważne z a || a = b
lub 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 ||= b
⇔a || a = b
?
Zachowanie tych instrukcji różni się, gdy a
jest niezdefiniowaną zmienną lokalną. W takim przypadku a ||= b
ustawi się a
na b
(i ocenia na b
), podczas gdy a || a = b
podniesie NameError: undefined local variable or method 'a' for main:Object
.
a ||= b
⇔a = 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 a
jest prawdziwa. W takim przypadku a ||= b
nie zrobi nic (prócz oceny a
), a a = a || b
zadzwoni a=(a)
do a
odbiornika. Jak zauważyli inni , może to mieć znaczenie, gdy wywoływanie a=a
ma skutki uboczne, takie jak dodawanie kluczy do skrótu.
a ||= b
⇔a = b unless a
??
Zachowanie tych stwierdzeń różni się tylko tym, co oceniają, kiedy a
jest zgodne z prawdą. W takim przypadku a = b unless a
oceni na nil
(choć a
nadal nie zostanie ustawiony zgodnie z oczekiwaniami), podczas gdy a ||= b
oceni na a
.
a ||= b
⇔ defined?(a) ? (a || a = b) : (a = b)
????
Nadal nie. Te instrukcje mogą się różnić, gdy method_missing
istnieje metoda, która zwraca prawdziwą wartość a
. W takim przypadku a ||= b
oceni do jakichkolwiek method_missing
zwrotów, a nie spróbuje ustawić a
, natomiast defined?(a) ? (a || a = b) : (a = b)
ustawi a
na b
i 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 ||= b
jest 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 ||= b
jest to równoważne tylko a || a = b
wtedy, gdy a
nieokreślona zmienna lokalna? Cóż, a = nil if false
zapewnia, że a
nigdy nie jest niezdefiniowany, nawet jeśli ta linia nigdy nie jest wykonywana. Zmienne lokalne w Ruby mają zasięg leksykalny.
(a=b unless a) or a
a
jest 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 a
powrót zajmuje dużo czasu lub ma skutki uboczne.
b
doa
, czy rhs wciąż przypisuje do lhs, czy innymi słowy, czy lhs nadal nie ustawia swojej wartości na rhs?
a ||= b
odpowiedź, jaką znalazłem w Internecie. Dzięki.
Przypuszczać a = 2
ib = 3
TO a ||= b
zostanie obliczone na a
wartość tj2
.
Tak jak wtedy, gdy ewaluacja do jakiejś wartości nie była wynikiem false
lub nil
… Dlatego ll
nie jest ocenianab
wartości.
Teraz Załóżmy a = nil
ib = 3
.
Wtedy a ||= b
będzie wynikał 3
tjb
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_user
nie został wcześniej zainicjowany.
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.
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.
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.
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
nil
albo false
nie tylkonil
Jeśli X
NIE 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
||=
przypisuje wartość do prawej tylko wtedy, gdy left == zero (lub jest niezdefiniowany lub fałszywy).
Ta składnia ruby-lang. Prawidłowa odpowiedź to sprawdzenie dokumentacji ruby-lang. Wszystkie inne wyjaśnienia są zaciemnione .
„ruby-lang docs Skrócone przypisanie”.
https://docs.ruby-lang.org/en/2.4.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment
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, b
wartość nie zostanie przypisana a
. a
nadal 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ść @users
zmiennej instancji.
||=
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 x
jest teraz równy 10. Jednak w drugim przykładzie x
jest już zdefiniowany jako 20. Zatem operator warunkowy nie ma wpływu. x
ma jeszcze 20 po uruchomieniu x ||= 10
.
a ||= b
to 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=nil
zero przed każdym zadaniem.