Что означает следующий код в 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 = false; a ||= trueвовсе не делать то , что ваш ответ говорит , что это делает «нюанс».
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 + ba ||= b примерно переводится как a || a = bЭто почти сокращение для a || a = b. Разница в том, что, когда значение aне определено, a || a = bбудет повышаться NameError, тогда как a ||= bустанавливается aна b. Это различие неважно, если aи bявляются локальными переменными, и имеет значение, если любой из них является методом получения / установки класса.
Дальнейшее чтение:
h = Hash.new(0); h[1] ||= 2. Теперь рассмотрим два возможных расширения h[1] = h[1] || 2против h[1] || h[1] = 2. Оба выражения имеют значение, 0но первое излишне увеличивает размер хэша. Возможно, именно поэтому Матц решил сделать ||=поведение более похожим на второе расширение. (Я основал это на примере одной из тем, связанных с другим ответом.)
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. Множество приближений, но ничего совершенно не эквивалентного ...
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. Оценка один раз и два раза не приводит к разнице в состоянии программы или системы.
afalse / zero / undefined, он оценивается дважды. (Но я не знаю Руби, поэтому я не знаю, можно ли точно оценить lvalues ...)
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нет.
a || a = bне будет оцениваться aдважды, когда aэто правда.
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.
Это означает или равно. Он проверяет, определено ли значение слева, а затем используйте его. Если это не так, используйте значение справа. Вы можете использовать его в Rails для кэширования переменных экземпляра в моделях.
Быстрый пример на основе Rails, где мы создаем функцию для извлечения текущего пользователя:
class User > ActiveRecord::Base
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
end
Он проверяет, установлена ли переменная экземпляра @current_user. Если это так, он вернет его, тем самым сохраняя вызов базы данных. Однако, если он не установлен, мы делаем вызов, а затем устанавливаем переменную @current_user. Это действительно простая техника кэширования, но она отлично подходит для случаев, когда вы выбираете одну и ту же переменную экземпляра в приложении несколько раз.
undefined, но и включается falseи nil, что может не иметь отношения к делу current_user, но особенно falseможет быть непредсказуемым в других случаях
x ||= y
является
x || x = y
«если x ложно или не определено, то x указывает на y»
Чтобы быть точным, a ||= bозначает «если aне определено или falsy ( falseили nil), установите aдля bи вычисляться (т.е. возвращение) b, в противном случае вычисляться a».
Другие часто пытаются проиллюстрировать это, говоря, что a ||= bэто эквивалентно a || a = bили a = a || b. Эти эквивалентности могут быть полезны для понимания концепции, но имейте в виду, что они не точны при любых условиях. Позвольте мне объяснить:
a ||= b⇔a || a = b ?
Поведение этих операторов отличается, когда aявляется неопределенной локальной переменной. В этом случае, 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приемник «s. Как уже отмечали другие , это может иметь значение, когда у вызова 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это функционально эквивалентно ... ( барабанная дробь )
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
aэто метод, он будет вызываться дважды, а не один раз (если он возвращает истинное значение в первый раз). Это может привести к отличиям в поведении, если, например, aвозвращение занимает много времени или имеет побочные эффекты.
bкa , не в шк еще назначить на LHS, или другими словами, не в л.ш. до сих пор установить его значение в правой части?
a ||= bответ, который я нашел в Интернете. Спасибо.
Предположим 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не инициализирован ранее.
a || = b
Указывает, присутствует ли какое-либо значение в «a», и вы не хотите изменять его, продолжая использовать это значение, иначе, если «a» не имеет никакого значения, используйте значение «b».
Простые слова, если слева, если не ноль, указывают на существующее значение, в противном случае указывают на значение справа.
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, а не выполнять его в первую очередь.
Пожалуйста, помните, что ||=это не атомарная операция и поэтому она не является поточно-ориентированной. Как правило, не используйте его для методов класса.
Это обозначение назначения по умолчанию
например: x || = 1
это проверит, чтобы видеть, является ли x нулем или нет. Если x действительно равен nil, ему будет присвоено это новое значение (1 в нашем примере)
более явно:
если x == nil
x = 1
end
nilили falseне толькоnil
Если значение 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
||= присваивает значение вправо, только если left == nil (или не определено или false).
Это синтаксис рубинового языка. Правильный ответ - проверить документацию ruby-lang. Все остальные объяснения запутывают .
"ruby-lang docs Сокращенное Назначение".
https://docs.ruby-lang.org/en/2.4.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment
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переменной экземпляра.
||= называется оператором условного присваивания.
Это в основном работает как, =но за исключением того, что, если переменная уже была назначена, она ничего не будет делать.
Первый пример:
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ноль перед каждым заданием.