Почему метод `method =` не обрабатывается каким-либо другим способом?

Рассмотрим следующий фрагмент кода:

class Example
  def my_attr=(value)
    @_my_attr = value
    @_my_attr * 3
  end
end

Я ожидаю, что выражение Example.new.my_attr = 5 вернет 15, но это окажется неправильным. Возвращаемое исходное значение всегда возвращается, даже если я вызываю метод = явно:

Example.new.my_attr = 5 # => 5
Example.new.my_attr=(5) # => 5

Как и почему Ruby делает это? Использует ли Ruby методы, которые заканчиваются на =, или это какой-то другой механизм? Я предполагаю, что это исключает цепочку для возвращаемых значений методов =, правильно? Есть ли способ заставить Ruby вести себя по-другому или это именно так?

Обновление: кредит для @jeffgran для этого:

Example.new.send(:my_attr=, 5) # => 15

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

Ответ 1

Так работает назначение; возвращаемое значение игнорируется, а результат выражения присваивания всегда является правым. Это фундаментальная черта грамматики Ruby. left-hand side = right-hand side всегда будет оцениваться до right-hand side, независимо от того, является ли левая сторона переменной (x), методом (object.x), константой (x) или любым выражением.

Источник: Языки программирования | Рубин Проект WGA по стандартизации IPA Ruby, 11.4.2.2.5, Назначения одиночных методов


Рассмотрим цепочку присвоений, x = y = 3.

Для правильной работы результат y = 3 должен быть 3, независимо от фактического значения, возвращаемого методом y=. x = y = 3 предназначен для чтения как y = 3; x = 3, а не как y = 3; x = y, что и подразумевалось бы, если возвращаемое значение из y= рассматривалось как результат y = 3.

Или рассмотрим все другие назначения мест. Иногда вместо этого...

obj.x = getExpensiveThing()
if obj.x 
  ...

... мы пишем это...

if obj.x = getExpensiveThing()

Это не может работать, если результат obj.x = ... может быть любой произвольной вещью, но мы знаем, что он будет работать, потому что результат obj.x = y всегда y.

Обновление

A comment в вопросе говорится:

Интересно, я не знал об этом сценарии. Кажется, что метод = возвращает любой ввод данных...

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

Все дело в том, что возвращаемое значение игнорируется грамматикой языка; присваивание не оценивает возвращаемое значение метода attr=, но возвращаемое значение все еще существует, о чем свидетельствует сам вопрос: Example.new.send(:my_attr=, 5) # => 15. Это работает, потому что это не присвоение. Вы перешагиваете ту часть языка Ruby.

Обновить снова

Чтобы быть ясным: x и y в моих примерах не должны интерпретироваться как буквальные переменные Ruby, они являются владельцами мест для любой действующей левой части задания. x или y может быть любое выражение: a, obj.a, CONSTANT_A, Something::a, @instance_a, оно все равно. Значение присваивания всегда является правым.