Почему двойная лопата в Ruby не мутирует состояние?

Я столкнулся с этим странным побочным эффектом, который вызвал ошибку или путаницу. Итак, представьте, что это не тривиальный пример, но, возможно, пример getcha.

name = "Zorg"

def say_hello(name)
  greeting = "Hi there, " << name << "?"
  puts greeting
end

say_hello(name)
puts name

# Hi there, Zorg?
# Zorg

Это не изменяет имя. Имя по-прежнему Zorg.

Но теперь посмотрим на очень тонкую разницу. в следующем примере:

name = "Zorg"

def say_hello(name)
  greeting = name << "?"
  puts "Hi there, #{greeting}"
end

say_hello(name)
puts name

# Hi there, Zorg?
# Zorg?  <-- name has mutated

Теперь имя Zorg?. Псих. Очень тонкая разница в назначении greeting =. Ruby делает что-то внутренне с разбором (?) Или цепочкой передачи сообщений? Я думал, что это просто сожжет лопаты, как name.<<("?"), но я думаю, что этого не происходит.

Вот почему я избегаю оператора лопаты при попытке выполнить конкатенацию. Обычно я стараюсь избегать мутирующего состояния, когда могу, но Ruby (в настоящее время) не оптимизирован для этого (пока). Возможно, Ruby 3 изменит ситуацию. Извините за масштабную/параллельную дискуссию о будущем Ruby.

Я думаю, что это особенно странно, так как пример с меньшими побочными эффектами (первый) имеет два оператора лопаты, где пример с большим количеством побочных эффектов имеет меньшее количество операторов лопаты.

Обновление Вы правы DigitalRoss, я делаю это слишком сложным. Уменьшенный пример:

one = "1"
two = "2"
three = "3"
message = one << two << three

Теперь, как вы думаете, все настроено? (не подглядывать!) Если бы я должен был догадаться, я бы сказал:

one is 123
two is 23
three is 3
message is 123

Но я бы ошибся в двух. Два равно 2.

Ответ 1

Если мы преобразуем вашу конструкцию a << b << c в более форму метода-иша и запустим кучу неявных скобок, поведение должно быть более четким. Переписывание:

greeting = "Hi there, " << name << "?"

дает:

greeting = ("Hi there, ".<<(name)).<<("?")

String#<< изменяет, но name никогда не отображается как цель /LHS <<, строка "Hi there ," << name но name нет. Если вы замените первый строковый литерал на переменную:

hi_there = 'Hi there, '
greeting = hi_there << name << '?'
puts hi_there

вы увидите, что << изменено hi_there; в вашем случае "Hi there, " это изменение было скрыто, потому что вы что-то изменяли (строковый литерал), после которого вы не могли смотреть.

Ответ 2

Вы делаете это слишком сложно.

Оператор возвращает левую часть, поэтому в первом случае он просто читает name (потому что сначала выполняется оценка "Hi there, " << name), но во втором примере он записывает его.

Теперь многие операторы Ruby являются право-ассоциативными, но << не является одним из них. См.: fooobar.com/questions/151965/...

Ответ 3

Правая часть вашего = оценивается слева направо.

Когда вы делаете

"Hello" << name << "?"

Операция начинается с "Hello", добавляет к ней name, затем добавляет "?" к мутированному "Hello".

Когда вы делаете

name << "?"

Операция начинается с name и добавляет к ней "?", мутируя имя (которое существует вне внутренней области метода.

Итак, в вашем примере one << two << three вы мутируете только one.