Что означает следующий код в Ruby?
||=
Есть ли у него какой-либо смысл или причина синтаксиса?
Что означает следующий код в Ruby?
||=
Есть ли у него какой-либо смысл или причина синтаксиса?
Этот вопрос обсуждался так часто в списках рассылки 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
не определено или неверно, то оцените 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 = b
a || a = b
. Разница в том, что когда a
не определено, a || a = b
a || a = b
вызовет NameError
, тогда как a ||= b
устанавливает a
в b
. Это различие неважно, если a
и 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
-
Изменить: как указывал AJedi32 в комментариях, это имеет значение true, если: 1. a - определенная переменная. 2. Оценка одного и двух раз не приводит к разнице в программном или системном состоянии.
Короче говоря, a||=b
означает: Если a
- undefined, nil or false
, назначьте b
в a
. В противном случае сохраните a
неповрежденным.
x ||= y
означает
если x
имеет какое-либо значение, оставьте его в покое и не изменяйте значение, в противном случае установите x
на 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 является ложным или undefined, то x указывает на y"
Чтобы быть точным, a ||= b
означает "если a
- undefined или ложь (false
или nil
), установите a
в b
и оцените (т.е. верните) b
, иначе оцените a
".
Другие часто пытаются проиллюстрировать это, сказав, что a ||= b
эквивалентно a || a = b
или a = a || b
. Эти эквивалентности могут быть полезны для понимания концепции, но имейте в виду, что они не точны при любых условиях. Позвольте мне объяснить:
a ||= b
⇔ a || a = b
?
Поведение этих операторов отличается, когда a
является локальной переменной undefined. В этом случае a ||= b
установит a
в b
(и оценит до b
), тогда как a || a = b
поднимет NameError: undefined local variable or method 'a' for main:Object
.
a ||= b
⇔ a = a || b
?
Эквивалентность этих утверждений часто принимается, поскольку аналогичная эквивалентность верна для других сокращенного присваивания операторов (т.е. +=
, -=
, *=
, /=
, %=
, **=
, &=
, |=
, ^=
, <<=
и >>=
). Однако для ||=
поведение этих операторов может отличаться, если a=
является методом объекта, а a
является правдивым. В этом случае a ||= b
ничего не сделает (кроме оценки a
), тогда как a = a || b
вызовет a=(a)
на приемнике a
. Как отметили другие, это может иметь значение при вызове a=a
имеет побочные эффекты, такие как добавление ключей к хешу.
a ||= b
⇔ a = b unless a
??
Поведение этих операторов отличается только тем, что они оценивают, когда a
является правдивым. В этом случае a = b unless a
будет оцениваться до nil
(хотя a
по-прежнему не будет установлен, как и ожидалось), тогда как a ||= b
будет оцениваться как a
.
a ||= b
⇔ defined?(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
функционально эквивалентен... (drumroll)
begin
a = nil if false
a || a = b
end
Держись! Разве это не первый пример с noop перед этим? Ну, не совсем. Помните, как я сказал ранее, что a ||= b
не эквивалентен a || a = b
, когда a
является локальной переменной undefined? Ну, a = nil if false
гарантирует, что a
никогда не undefined, хотя эта строка никогда не выполняется. Локальные переменные в Ruby лексически ограничены.
Предположим a = 2
и b = 3
THEN, a ||= b
будет приведено к значению a
i.e. 2
.
Как и когда a оценивает до некоторого значения, не приведенного к false
или nil
.. Поэтому он ll
не оценивает значение b
.
Теперь предположим a = nil
и b = 3
.
Затем a ||= b
будет приведено к значению 3
i.e b
.
Сначала он пытается оценить значение, которое привело к 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
не инициализируется раньше.
unless x
x = y
end
если значение x не имеет значения (оно не равно nil или false), установите его равным y
эквивалентно
x ||= y
a ||= b
эквивалентно
a || a = b
а не
a = a || b
из-за ситуации, когда вы определяете хэш с по умолчанию (хэш возвращает значение по умолчанию для любых undefined)
a = Hash.new(true) #Which is: {}
если вы используете:
a[10] ||= 10 #same as a[10] || a[10] = 10
a все еще:
{}
но когда вы пишете его так:
a[10] = a[10] || 10
a становится:
{10 => true}
потому что вы назначили значение самого себя в ключе 10
, которое по умолчанию имеет значение true, поэтому теперь хэш определен для ключа 10
, а не для выполнения задания в первую очередь.
Это как ленивый экземпляр. Если переменная уже определена, она будет принимать это значение вместо создания значения снова.
Это обозначение назначения по умолчанию
например: x || = 1
это проверит, чтобы видеть, является ли x нулем или нет. Если x действительно равен nil, ему будет присвоено это новое значение (1 в нашем примере)
более явно:
если х == ноль
х = 1
конец
Также помните, что ||=
не является атомной операцией, и поэтому он не является потокобезопасным. Как правило, не используйте его для методов класса.
Как общее заблуждение, a ||= b
не эквивалентно a = a || b
a = a || b
, но он ведет себя как a || a = b
a || a = b
.
Но тут возникает сложный случай. Если a
не определено, a || a = 42
a || a = 42
вызывает NameError
, а a ||= 42
возвращает 42
. Таким образом, они не кажутся эквивалентными выражениями.
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
экземпляра.
|| = - оператор условного присваивания
x ||= y
эквивалентно
x = x || y
или альтернативно
if defined?(x) and x
x = x
else
x = y
end
a || = b
Указывает, присутствует ли какое-либо значение в "a", и вы не хотите изменять его, продолжая использовать это значение, иначе, если "a" не имеет никакого значения, используйте значение "b".
Простые слова, если слева, если не ноль, указывают на существующее значение, в противном случае указывают на значение справа.
||=
присваивает значение правому значению, только если left == nil (или не определено, либо false).
||=
называется оператором условного присваивания.
В основном он работает как =
, но за исключением того, что если переменная уже назначена, она ничего не сделает.
Первый пример:
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
nil перед каждым заданием.