Що означає || = (або дорівнює) означає у Ruby?


340

Що означає наступний код у Ruby?

||=

Чи має воно якесь значення чи причину для синтаксису?

Відповіді:


175

Це питання настільки часто обговорювалося в розсилках Ruby-розсилках та блогах Ruby, що зараз є навіть потоки в списку розсилки Ruby, єдиною метою яких є збирання посилань на всі інші потоки в списку розсилки Ruby, які обговорюють цю проблему. .

Ось один: остаточний список || = (АБО рівних) тем та сторінок

Якщо ви дійсно хочете знати, що відбувається, перегляньте розділ 11.4.2.3 "Скорочені завдання" проекту технічної мови Ruby Language .

Як перше наближення,

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зовсім НЕ робити те , що ваша відповідь говорить , що це робить «нюанс».
Jörg W Mittag

23
Можливо, це питання задавали стільки разів, тому що люди постійно відповідають, що це запитання було задано стільки разів.
einnocent

8
З цією відповіддю легко зрозуміти, чому існує кілька потоків. Якщо ви спробуєте знайти відповідь на це питання за допомогою шапки-початківця, ви помітите, що всі відповіді не зрозумілі. Наприклад, з цим ви просто говорите, що ні. Я пропоную вдосконалити свою відповідь і дати легкі відповіді для новачків: a = b, якщо не
Арнольд Роа

594

a ||= bє оператором умовного призначення . Це означає, що якщо aневизначено чи фальсифіковано , то оцініть bі встановіть aрезультат . Еквівалентно, якщо aце визначено та оцінюється на truthy, то 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обидві локальні змінні, але є суттєвою, якщо будь-який метод getter / setter класу.

Подальше читання:


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
Мені подобається інша відповідь за те, наскільки вона поглиблена, але мені подобається ця відповідь за її простоту. Для того, щоб хтось вивчив Рубі, це тип відповіді, який нам потрібен. Якби ми знали, що означає || =, тоді питання, ймовірно, було б сформульовано інакше.
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
Ви впевнені? Це означає, що якщо aзначення false / zero / undefined, воно оцінюється двічі. (Але я не знаю Рубі, тож я не знаю, чи можна точно оцінити lvalues ​​...)
Стів Беннетт

Я бачу, що ти кажеш. Я мав на увазі, що два рядки є рівнозначними - це те, що кінцевий стан буде еквівалентним після того, як буде оцінено весь рядок, що означає значення a, b і те, що повертається. Незалежно від того, чи використовують рубінові перекладачі різних станів - як, наприклад, кілька оцінок ", - потрапити туди цілком можливо. Будь-який експерт з перекладу рубіну там?
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 та посилання, що містяться в ньому.
Йорг W Міттаг

1
@Jo (umlaut) rg, я не бачу, що в цьому погано. Ваше посилання - це список інших посилань. Немає реального пояснення, чому це неправильно, просто звучить як ціннісний суд щодо вашого кінця.
яєчники

ця відповідь є помилковою, тому що вона не тільки спрацьовує 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його приймача. Як зазначали інші , це може змінити ситуацію, коли виклик 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існує метод, який повертає значення "truthy" 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

Зачекай! Хіба це не лише перший приклад, коли перед ним лежить ніоп? Ну, не зовсім. Пам'ятайте, як я говорив раніше, 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

еквівалентно

x ||= y


3

Припустимо a = 2іb = 3

ТОГО, a ||= b буде виведено значення a', тобто 2.

Як коли коли оцінюється якесь значення, яке не призвело до falseабо nil.. Тому воно llне оцінює bзначення.

Тепер припустимо a = nilі b = 3.

Тоді a ||= bбуде виведено значення 3ie 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раніше не ініціалізовано.


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 все ще:

{}

але коли ти пишеш це так:

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

a стає:

{10 => true}

тому що ви присвоїли значення себе за ключем 10, яке за замовчуванням відповідає істині, тепер хеш визначається для ключа 10, а не ніколи не виконує призначення в першу чергу.


2

Це як ледача інстанція. Якщо змінна вже визначена, вона прийме це значення, а не створювати це знову.


2

Також пам’ятайте, що ||=це не атомна операція, і це не безпечно для потоків. Як правило, не використовуйте його для методів класу.


2

Це нотація про призначення за замовчуванням

наприклад: x || = 1
це перевірить, чи є х нульовим чи ні. Якщо x дійсно дорівнює нулю, він присвоїть йому нове значення (1 у нашому прикладі)

більш чітко:
якщо x == nil
x = 1
кінець


або nilабо false, не тількиnil
Алекс Пока

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

||= присвоює значення праворуч лише у тому випадку, якщо ліворуч == нуль (або невизначено або невірно).


ви, ймовірно, мали на увазі "присвоює значення ліворуч", а не праворуч
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.