Почему умножение не всегда коммутативно в Ruby?

Если x не является целым числом, я получаю такой результат:

x = "a"
x * 6 #=> aaaaaa
6 * x #=> TypeError: String can't be coerced into Fixnum

тогда как если x является целым числом:

x = 6
x * 6 #=> 36
6 * x #=> 36

Странно, что порядок операндов при умножении имеет значение, если x не является целым числом, а не если x является целым числом. Кто-нибудь может объяснить, что за рациональное стоит за этим? Когда x является строкой, почему переменная x должна предшествовать оператору *, чтобы избежать возникновения ошибки?


person roppo    schedule 30.07.2017    source источник
comment
Приоритет не имеет ничего общего с этой ситуацией.   -  person Sergio Tulentsev    schedule 30.07.2017


Ответы (3)


  1. У вас опечатка в последнем фрагменте: он должен начинаться с x = 6 (без кавычек).

  2. Все в Ruby является объектом, включая экземпляры String, Integer, даже nil, который является [единственным] экземпляром NilClass.

При этом нет просто оператора *. Это старый добрый метод, объявленный в разных классах, который вызывается оператором * (спасибо @SergioTulentsev за придирчивый комментарий к формулировке). Вот документ для String#*, другие вы можете найти сами. А "a" * 6 есть не что иное, как:

"a".*(6)

Вы можете проверить приведенное выше в своей консоли: это совершенно правильный код Ruby. Таким образом, разные классы имеют разные реализации метода *, отсюда и разные результаты выше.

person Aleksei Matiushkin    schedule 30.07.2017
comment
Это является оператором, хотя. Это разрешается в обработчик сообщений (метод), да, но это распознается как особый случай в языке. Как следствие, вы не можете определять произвольные операторы (поскольку синтаксический анализатор не знает о них ). - person Sergio Tulentsev; 30.07.2017
comment
Все в Ruby является объектом, включая String и Integer. Хотя String и Integer тоже являются объектами, вы, вероятно, имеете в виду экземпляры String и Integer. - person Stefan; 31.07.2017

Здесь вы пытаетесь использовать три шаблона:

  • a. string * numeric
  • b. numeric * string
  • c. numeric * numeric

Поведение метода и требуемые аргументы в первую очередь зависят от того, что находится в левой части метода (в данном случае *), на котором этот метод определен. Ни один метод (включая *) сам по себе не является коммутативным.

String#* требует, чтобы первый аргумент был числовым, что a. удовлетворяет, а Numeric#* требует, чтобы первый аргумент был числовым, что c. удовлетворяет, и б. не.

person sawa    schedule 30.07.2017
comment
Есть ли рациональное значение для строки/числового значения, чтобы первый аргумент был числовым? Или это просто то, как работает Ruby? - person roppo; 31.07.2017
comment
Это не имеет ничего общего с Ruby в частности. Так работает ОО. Получатель сообщения решает, что ему делать с сообщением. Это фундаментальная особенность объектно-ориентированного программирования. ОО принципиально некоммутативен. В a.foo(b) именно a решает, как интерпретировать foo, в b.foo(a) решает b. Для того чтобы foo было коммутативным, a и b должны договориться о совместном определении foo. В данном случае это не так. - person Jörg W Mittag; 31.07.2017

Вам нужно понять, что делает метод *1. Это зависит от получателя метода. Для "cat".*(3) "cat" является получателем *. Для 1.*(3) (которое, как будет объяснено позже, может быть записано как 1*3) 1 является получателем *. Термин «получатель» происходит от концепции ООП об отправке сообщения (метода) получателю.

Метод может быть определен для объекта (например, "cat" или 1) одним из двух способов. Чаще всего метод является методом-экземпляром, определенным в классе получателя (например, * определен в "cat".class #=> String или 1.class #=> Integer). Второй способ, который здесь неприменим, заключается в том, что метод был определен в одноэлементном классе объекта, если он есть у объекта (у "cat" есть одноэлементный класс, а у 1, являющегося непосредственным значением, его нет).

Поэтому, когда мы видим "cat".*(3), мы ищем в документе String#* и сделать вывод, что

"cat".*(3) #=> "catcatcat"

Для 1*(3) мы ищем Integer#*, что говорит нам о том, что

1.*(3) #=> 3

Давайте попробуем другое: [1,2,3].*(3), поскольку [1,2,3].class #=> Array мы смотрим на массив #* и делаем вывод, что

[1,2,3].*(3) #=> [1, 2, 3, 1, 2, 3, 1, 2, 3]

Обратите внимание, что этот метод имеет две формы, в зависимости от того, является ли его аргумент целым числом (как здесь) или строкой. В последнем случае

[1,2,3].*(' or ') #=> "1 or 2 or 3"

Многие методы имеют различное поведение, которое зависит от его аргументов (и от того, предоставлен ли необязательный блок).

Наконец, Ruby позволяет нам использовать сокращение для этих трех методов (и некоторых других с именами, состоящими из символов, которые не являются буквами, цифрами или символами подчеркивания):

"cat"*3     #=> "catcatcat"
"cat" * 3   #=> "catcatcat"
1*3         #=> 3
[1,2,3] * 3 #=> [1, 2, 3, 1, 2, 3, 1, 2, 3]

Это сокращение обычно называют «синтаксическим сахаром».

1 Имена методов Ruby не ограничиваются словами, такими как "map", "upcase" и т.д. "*", "~", "[]" и "[]=", например, являются допустимыми именами методов"

person Cary Swoveland    schedule 30.07.2017
comment
Как упоминалось в моем комментарии к ответу @mudasobwa, список методов, у которых есть этот сахар, жестко закодирован в синтаксическом анализаторе. Таким образом, это не может быть какое-либо небуквенно-цифровое имя. - person Sergio Tulentsev; 30.07.2017
comment
Возможно, стоит отметить, что результат также может отличаться при изменении аргумента (вместо получателя), например. [1,2,3] * "," возвращает строку "1,2,3" - person Stefan; 31.07.2017