Почему двойная лопата в 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
.