Что означает || = (или-равно) в Ruby?


340

Что означает следующий код в Ruby?

||=

Имеет ли это какое-либо значение или причину для синтаксиса?

Ответы:


175

Этот вопрос обсуждался так часто в списках рассылки Ruby и блогах Ruby, что теперь в списке рассылки Ruby есть даже темы, единственная цель которых - собирать ссылки на все остальные темы в списке рассылки Ruby, которые обсуждают эту проблему. ,

Вот один из них: полный список тем и страниц || = (ИЛИ равно)

Если вы действительно хотите знать, что происходит, взгляните на Раздел 11.4.2.3 «Сокращенные назначения» проекта спецификации языка Ruby .

В первом приближении

a ||= b

эквивалентно

a || a = b

и не эквивалентно

a = a || b

Однако это только первое приближение, особенно если aоно не определено. Семантика также различается в зависимости от того, является ли это простым назначением переменной, назначением метода или назначением индексации:

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

все относятся по-разному.


2
Вторая ссылка пострадала от гниения (комментарий от meta от stackoverflow.com/users/540162/nightfirecat ).
Эндрю Гримм

331
Это очень загадочный не ответ. Краткий ответ: a || = b означает, что если a не определено, присвойте ему значение b, иначе оставьте его в покое. (Хорошо, есть нюансы и особые случаи, но это основной случай.)
Стив Беннетт

20
@SteveBennett: Я бы не назвал тот факт , что a = false; a ||= trueвовсе не делать то , что ваш ответ говорит , что это делает «нюанс».
Йорг Миттаг

23
Может быть, этот вопрос задавался так много раз, потому что люди продолжают отвечать, что этот вопрос задавался так много раз.
einnocent

8
С помощью этого ответа легко понять, почему существует несколько потоков. Если вы попытаетесь найти ответ на этот вопрос, используя шляпу новичка, вы заметите, что все ответы не ясны. Например, с этим вы просто говорите, что нет. Я предлагаю улучшить ваш ответ и дать простые ответы новичкам: a = b, если a
Арнольд Роа

594

a ||= bявляется условным оператором присваивания . Это означает, что если значение aundefined или false , то оцените bи установите aрезультат . Эквивалентно, если aопределено и оценивается как правдивое, то bне оценивается, и присвоение не происходит. Например:

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

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

Это сбивает с толку, оно похоже на другие операторы присваивания (например, +=), но ведет себя по-другому.

  • a += b переводит на a = a + b
  • a ||= b примерно переводится как a || a = b

Это почти сокращение для a || a = b. Разница в том, что, когда значение aне определено, a || a = bбудет повышаться NameError, тогда как a ||= bустанавливается aна b. Это различие неважно, если aи bявляются локальными переменными, и имеет значение, если любой из них является методом получения / установки класса.

Дальнейшее чтение:


52
Спасибо за этот ответ, это имеет гораздо больше смысла.
Том Херт

не искал достаточно, но до сих пор не понимаю, почему вы использовали бы это, в отличие от a = a || б. возможно только мое личное мнение, но немного смешно, что такой нюанс существует ...
dtc

2
@dtc, рассмотрим h = Hash.new(0); h[1] ||= 2. Теперь рассмотрим два возможных расширения h[1] = h[1] || 2против h[1] || h[1] = 2. Оба выражения имеют значение, 0но первое излишне увеличивает размер хэша. Возможно, именно поэтому Матц решил сделать ||=поведение более похожим на второе расширение. (Я основал это на примере одной из тем, связанных с другим ответом.)
antinome

1
Мне нравится другой ответ о его глубоком понимании, но мне нравится этот ответ за его простоту. Для тех, кто изучает Ruby, этот тип ответа нам нужен. Если бы мы знали, что означает || =, тогда вопрос, вероятно, был бы сформулирован иначе.
OBCENEIKON

1
Fyi, a || a = bподнимает, NameErrorесли aнеопределено. a ||= bнет, но вместо этого инициализирует aи устанавливает его b. Это единственное различие между этими двумя, насколько я знаю. Точно так же единственное различие между a = a || bи тем, a ||= bчто я знаю, заключается в том, что если a=это метод, он будет вызываться независимо от того, что aвозвращает. Кроме того , единственное различие между a = b unless aи a ||= bчто я знаю, что это заявление имеет значение , nilа не aесли aэто truthy. Множество приближений, но ничего совершенно не эквивалентного ...
Ajedi32

32

Краткий и полный ответ

a ||= b

оценивается так же, как каждая из следующих строк

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

-

С другой стороны,

a = a || b

оценивается так же, как каждая из следующих строк

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

-

Редактировать: Как AJedi32 указал в комментариях, это верно только в том случае, если: 1. a является определенной переменной. 2. Оценка один раз и два раза не приводит к разнице в состоянии программы или системы.


1
ты уверен? Это подразумевает, что если afalse / zero / undefined, он оценивается дважды. (Но я не знаю Руби, поэтому я не знаю, можно ли точно оценить lvalues ​​...)
Стив Беннетт

Я понимаю, что вы говорите. Под двумя эквивалентными строками я подразумеваю, что конечное состояние будет эквивалентным после оценки всей строки, то есть значения a, b и того, что возвращается. Независимо от того, используют ли рубиновые интерпретаторы разные состояния - например, несколько оценок a - для достижения цели вполне возможно. Есть какие-нибудь эксперты по переводчикам?
the_minted

3
Это не совсем верно. a || a = b, a ? a : a = b, if a then a else a = b end, И if a then a = a else a = b endвыдаст ошибку , если aне определено, тогда a ||= bи a = a || bне будет. Кроме того , a || a = b, a ? a : a = b, if a then a else a = b end, a = a ? a : b, и if a then a = a else a = b endоценить в aдва раза , когда aэто truthy, в то время как a ||= bи a = a || bнет.
Ajedi32

1
* коррекция: a || a = bне будет оцениваться aдважды, когда aэто правда.
Ajedi32

1
@the_minted the end state will be equivalent after the whole line has been evaluatedЭто не обязательно правда, хотя. Что если aэто метод? Методы могут иметь побочные эффекты. Например, С public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5, self.a ||= bвернется 6, но self.a ? self.a : self.a = bвернется 7.
Ajedi32

27

Короче говоря, a||=bозначает: Если aесть undefined, nil or false, правопреемник bв a. В противном случае, оставайтесь aнетронутыми.


16
В принципе,


x ||= y средства

если xимеет какое-либо значение, оставьте его в покое и не изменяйте значение, в противном случае установите xзначениеy


13

Это означает или равно. Он проверяет, определено ли значение слева, а затем используйте его. Если это не так, используйте значение справа. Вы можете использовать его в Rails для кэширования переменных экземпляра в моделях.

Быстрый пример на основе Rails, где мы создаем функцию для извлечения текущего пользователя:

class User > ActiveRecord::Base

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

end

Он проверяет, установлена ​​ли переменная экземпляра @current_user. Если это так, он вернет его, тем самым сохраняя вызов базы данных. Однако, если он не установлен, мы делаем вызов, а затем устанавливаем переменную @current_user. Это действительно простая техника кэширования, но она отлично подходит для случаев, когда вы выбираете одну и ту же переменную экземпляра в приложении несколько раз.


8
Это не верно. Пожалуйста, прочитайте Ruby-Forum.Com/topic/151660 и ссылки в нем.
Йорг Миттаг

1
@ Джо (умлаут), я не вижу, что в этом плохого. Ваша ссылка представляет собой список других ссылок. Нет реального объяснения, почему это неправильно, просто звучит как ценностное суждение с вашей стороны.
яйцеклетки

этот ответ неверен, потому что он не только включается undefined, но и включается falseи nil, что может не иметь отношения к делу current_user, но особенно falseможет быть непредсказуемым в других случаях
dfherr

Несмотря на всю неполноту, которую может продемонстрировать этот ответ (не работает для nil / false), это первое, что объясняет, почему вы хотите использовать || =, так что спасибо!
Джонатан Тузман


8

Чтобы быть точным, a ||= bозначает «если aне определено или falsy ( falseили nil), установите aдля bи вычисляться (т.е. возвращение) b, в противном случае вычисляться a».

Другие часто пытаются проиллюстрировать это, говоря, что a ||= bэто эквивалентно a || a = bили a = a || b. Эти эквивалентности могут быть полезны для понимания концепции, но имейте в виду, что они не точны при любых условиях. Позвольте мне объяснить:

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

    Поведение этих операторов отличается, когда aявляется неопределенной локальной переменной. В этом случае, a ||= bбудет установлен aв b(и оценивать с b), в то время как a || a = bподнимут NameError: undefined local variable or method 'a' for main:Object.

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

    Эквивалентность этих утверждений часто предполагается, так как аналогичные эквивалентности верно и для других сокращенно назначений операторов (т.е. +=, -=, *=, /=, %=, **=, &=, |=, ^=, <<=, и >>=). Однако ||=поведение этих операторов может отличаться, когда a=метод является объектом и aявляется правдивым. В этом случае a ||= bне будет делать ничего (кроме вычисляться a), в то время как a = a || bпозвонит a=(a)на aприемник «s. Как уже отмечали другие , это может иметь значение, когда у вызова a=aесть побочные эффекты, такие как добавление ключей в хеш.

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

    Поведение этих утверждений отличается только тем, что они оценивают, когда aправдиво. В этом случае a = b unless aбудет оцениваться nil(хотя aвсе равно не будет установлено, как ожидается), тогда как a ||= bбудет оцениваться a.

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

    Все еще нет. Эти утверждения могут отличаться, когда method_missingсуществует метод, который возвращает истинное значение для a. В этом случае, a ||= bбудет оценивать по каким - то method_missingвозвращается, а не пытаться установить a, в то время как defined?(a) ? (a || a = b) : (a = b)будет установлен aв bи вычисляться b.

Хорошо, хорошо, так что же a ||= b эквивалентно? Есть ли способ выразить это в Ruby?

Ну, если предположить, что я ничего не пропускаю, я считаю, что a ||= bэто функционально эквивалентно ... ( барабанная дробь )

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

Оставайтесь на линии! Разве это не первый пример с noop перед ним? Ну, не совсем. Помните, как я говорил ранее, a ||= bэто не эквивалентно тому, a || a = bкогда aнеопределенная локальная переменная? Хорошо, a = nil if falseгарантирует, что aэто никогда не будет неопределенным, даже если эта строка никогда не выполняется. Локальные переменные в Ruby имеют лексическую область видимости.


Итак, ваш расширенный третий пример:(a=b unless a) or a
vol7ron

1
@ vol7ron Это имеет аналогичную проблему, как # 2. Если aэто метод, он будет вызываться дважды, а не один раз (если он возвращает истинное значение в первый раз). Это может привести к отличиям в поведении, если, например, aвозвращение занимает много времени или имеет побочные эффекты.
Ajedi32

Кроме того , первое предложение, не должны это сказать , правопреемником bкa , не в шк еще назначить на LHS, или другими словами, не в л.ш. до сих пор установить его значение в правой части?
Vol7ron

Лучший a ||= bответ, который я нашел в Интернете. Спасибо.
Эрик

3

unless x x = y end

если х не имеет значения (это не ноль или ложь), установите его равным у

эквивалентно

x ||= y


3

Предположим a = 2иb = 3

ТОГДА, a ||= b будет приведено к a«S значение , т.е. 2.

Как, например, когда a оценивает какое-то значение, не приведенное к falseили nil.. Поэтому оно llне оценивает bзначение.

Теперь предположим a = nilи b = 3.

Тогда a ||= bбудет приведено значение 3ie b.

Когда он сначала попытается оценить значение a, которое привело к nil..., он также оценил bзначение.

Лучший пример, используемый в приложении 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

Где, User.find_by_id(session[:user_id])запускается тогда и только тогда, когда @current_userне инициализирован ранее.


3

a || = b

Указывает, присутствует ли какое-либо значение в «a», и вы не хотите изменять его, продолжая использовать это значение, иначе, если «a» не имеет никакого значения, используйте значение «b».

Простые слова, если слева, если не ноль, указывают на существующее значение, в противном случае указывают на значение справа.


2
a ||= b

эквивалентно

a || a = b

и нет

a = a || b

из-за ситуации, когда вы определяете хеш со значением по умолчанию (хеш вернет значение по умолчанию для любых неопределенных ключей)

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

если вы используете:

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

А еще:

{}

но когда вы пишете это так:

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

становится:

{10 => true}

потому что вы присвоили значение себе в ключе 10, которое по умолчанию равно true, поэтому теперь для ключа определен хэш 10, а не выполнять его в первую очередь.


2

Это как ленивый экземпляр. Если переменная уже определена, она примет это значение вместо того, чтобы создавать значение снова.


2

Пожалуйста, помните, что ||=это не атомарная операция и поэтому она не является поточно-ориентированной. Как правило, не используйте его для методов класса.


2

Это обозначение назначения по умолчанию

например: x || = 1
это проверит, чтобы видеть, является ли x нулем или нет. Если x действительно равен nil, ему будет присвоено это новое значение (1 в нашем примере)

более явно:
если x == nil
x = 1
end


либо nilили falseне толькоnil
Алекс Poca

2

|| = - оператор условного присваивания

  x ||= y

эквивалентно

  x = x || y

или в качестве альтернативы

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

2

Если значение XНЕ имеет, ему будет присвоено значение Y. Иначе, это сохранит свое первоначальное значение, 5 в этом примере:

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

Как распространенное заблуждение, a ||= bне эквивалентно a = a || b, но ведет себя как a || a = b.

Но тут возникает сложный случай. Если aне определено, a || a = 42повышается NameError, пока a ||= 42возвращается 42. Таким образом, они не кажутся эквивалентными выражениями.


1

||= присваивает значение вправо, только если left == nil (или не определено или false).


Вы, вероятно, имели в виду «присваивает значение левому» вместо правого
Maysam Torabi


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

Потому что aуже был установлен1

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

Потому что aбылnil


Какова дата ответа здесь. Почему это не показывает год?
Шив

0
b = 5
a ||= b

Это переводится как:

a = a || b

которые будут

a = nil || 5

так наконец

a = 5

Теперь, если вы позвоните это снова:

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

b = 6

Теперь, если вы позвоните это снова:

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

Если вы наблюдаете, bзначение не будет присвоено a. aвсе еще будет 5.

Это шаблон памятки, который используется в Ruby для ускорения доступа.

def users
  @users ||= User.all
end

Это в основном означает:

@users = @users || User.all

Таким образом, вы вызовете базу данных в первый раз, когда вызовете этот метод.

Будущие вызовы этого метода просто вернут значение @usersпеременной экземпляра.


0

||= называется оператором условного присваивания.

Это в основном работает как, =но за исключением того, что, если переменная уже была назначена, она ничего не будет делать.

Первый пример:

x ||= 10

Второй пример:

x = 20
x ||= 10

В первом примере xтеперь равно 10. Однако во втором примере xуже определено как 20. Таким образом, условный оператор не имеет эффекта. xеще 20 после бега x ||= 10.


-2

a ||= bэто то же самое, что сказать a = b if a.nil?илиa = b unless a

Но все ли 3 варианта показывают одинаковую производительность? С Ruby 2.5.1 это

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

занимает на моем ПК 0.099 секунды, в то время как

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

занимает 0,062 секунды. Это почти на 40% быстрее.

и тогда мы также имеем:

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

что занимает 0,166 секунды.

Не то чтобы это оказало значительное влияние на производительность в целом, но если вам нужен последний бит оптимизации, рассмотрите этот результат. Кстати: a = 1 unless aновичку легче читать, это само за себя.

Примечание 1: причина повторения строки назначения несколько раз состоит в том, чтобы уменьшить накладные расходы цикла на измеренное время.

Примечание 2: результаты аналогичны, если я делаю a=nilноль перед каждым заданием.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.