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

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

||=

имеет ли он какой-либо смысл или причину для синтаксиса?

20 ответов


этот вопрос так часто обсуждался в списках рассылки 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

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


a ||= b является "оператором условного присвоения". Это вроде-но-не-совсем(*) сокращение для a || a = b.

это значит "если a неопределено или falsey (false или nil), затем оценки b и set a в результате".

например:

> a ||= nil
=> nil
> a ||= 0;
=> 0
> a ||= 2;
=> 0

> foo = false;
=> false
> foo ||= true;
=> true
> foo ||= false;
=> true

оценка короткого замыкания Ruby означает, что если a определено и оценивает к truthy, тогда правая сторона оператор не оценивается, и назначение не выполняется. Это различие не имеет значения, если a и b как локальные переменные, но значительна, если это геттер/сеттер класса.

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

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

a ||= b грубо говоря* a || a = b

*кроме того, когда a неопределено, a || a = b было бы NameError, тогда как a ||= b наборы a to b.

читайте далее:


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

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

-

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


короче, a||=b означает: если a is undefined, nil or false, назначить b to a. В противном случае, держите a нетронутыми.


В основном,


x ||= y означает

если x имеет любое значение оставьте его в покое и не изменяйте значение, иначе set x to y.


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

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

class User > ActiveRecord::Base

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

end

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


x ||= y

и

x || x = y

"Если x ложно или неопределенно, то X указывает на y"


если быть точным,a ||= b означает: "если a не определено или ложно (false или nil), set a to b и оценить (т. е. возвращение) b, в противном случае оцените до a".

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

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

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

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

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

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

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

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

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

Ладно, ладно, так что is a ||= b эквивалент чтобы? Есть ли способ выразить это в Ruby?

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

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

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


unless x x = y end

Если x не имеет значения (это не nil или false), установите его равным y

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

x ||= y


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

затем, a ||= b будет приведено к aзначение т. е. 2.

как когда A оценивает некоторое значение, не приведенное к false или nil.. Вот почему это ll не оценки b's значение.

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

затем a ||= b будет приведено к 3 то есть b's значение.

как он сначала пытается оценить значение a, которое привело к nil.. поэтому он оценил b's значение.

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


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

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

более явный:
если x == nil
x = 1
конец


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

a становится:

{10 => true}

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


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


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


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


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.

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

def users
  @users ||= User.all
end

этот в основном переводится как:

@users = @users || User.all

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

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


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

но вот идет хитрый случай

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


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

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

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

x ||= 10

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

x = 20
x ||= 10

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


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 ноль перед каждым заданием.


|/= это условный оператор присваивания

  x ||= y

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

  x = x || y

или

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