1

If x is a non-integer, I get this result:

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

whereas if x is an integer:

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

It's strange that the operand order in multiplication matters if x is a non-integer, and not if x is an integer. Can someone explain what the rational is behind this? When x is a string, why must variable x precede the * operator to avoid raising an error?

Stefan
  • 109,145
  • 14
  • 143
  • 218
roppo
  • 57
  • 7

3 Answers3

4
  1. You have a typo in your latter snippet: it should start with x = 6 (without quotes.)

  2. Everything in Ruby is an object, including instances of String, Integer, even nil which is [the only] instance of NilClass.

That said, there is no just an operator *. It’s a plain old good method, declared on different classes, that is called by the operator * (thanks @SergioTulentsev for picky wording comment.) Here is a doc for String#*, other you might find yourself. And "a" * 6 is nothing else, than:

"a".*(6)

You might check the above in your console: it’s a perfectly valid Ruby code. So, different classes have different implementations of * method, hence the different results above.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • It [_is_ an operator](https://github.com/ruby/ruby/blob/trunk/parse.y#L1935), though. It is resolved to a message handler (method), yes, but it is recognized as a special case in the language. As a consequence, you [can't define arbitrary operators](https://gist.github.com/stulentsev/ba5311c3c796f024bc6c59227448d491) (because parser doesn't know about them). – Sergio Tulentsev Jul 30 '17 at 17:56
  • _"Everything in Ruby is an object, including String, Integer"_ – although `String` and `Integer` are objects too, you are probably referring to **instances** of `String` and `Integer`. – Stefan Jul 31 '17 at 08:14
4

You are trying three patterns here:

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

The behavior of a method and what arguments are required primarily depends on what is on the left side of the method (* in this case), on which the method is defined. No method (including *) is commutative per se.

String#* requires the first argument to be a numeric, which a. satisfies, and Numeric#* requires the first argument to be a numeric, which c. satisfies, and b. does not.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • Is there a rational for string/numeric to require the first argument to be a numerical? Or this is just how Ruby works? – roppo Jul 31 '17 at 10:55
  • 1
    This has nothing to do with Ruby in particular. This is how OO works. The receiver of a message gets to decide what he does with the message. That is the *fundamental* feature of OO. OO is *fundamentally* non-commutative. In `a.foo(b)`, it is `a` which gets to decide how to interpret `foo`, in `b.foo(a)`, it is `b` which gets to decide. In order for `foo` to be commutative, `a` and `b` have to agree to cooperate on a shared definition of `foo`. In this case, they don't. – Jörg W Mittag Jul 31 '17 at 19:50
3

You need to understand what the method * does.1. That depends on the method's receiver. For "cat".*(3), "cat" is *'s receiver. For 1.*(3) (which, as explained later, can be written, 1*3) 1 is *'s receiver. The term "receiver" derives from OOP's concept of sending a message (method) to a receiver.

A method can be defined on an object (e.g., "cat" or 1) in one of two ways. The most common is that the method is an instance method defined on the receiver's class (e.g., * defined on "cat".class #=> String or 1.class #=> Integer. The second way, which is not applicable here, is that the method has been defined on the object's singleton class, provided the object has one. ("cat" has a singleton class but 1, being an immediate value, does not.)

When we see "cat".*(3), therefore, we look to the doc for String#* and conclude that

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

For 1*(3), we look to Integer#*, which tells us that

1.*(3) #=> 3

Let's try another: [1,2,3].*(3), Because [1,2,3].class #=> Array we look to Array#* and conclude that

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

Note that this method has two forms, depending on whether its argument is an integer (as here) or a string. In the latter case

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

Many methods have different behaviors that depend on its arguments (and on whether an optional block is provided).

Lastly, Ruby allows us to use a shorthand with these three methods (and certain others with names comprised of characters that are not letters, numbers or underscores.):

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

This shorthand is generally referred to as "syntactic sugar".

1 Ruby's method names are not restricted to words, such as "map", "upcase" and so on. "*", "~", "[]" and "[]=", for example, are valid method names"

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    As mentioned in my comment to @mudasobwa's answer, the list of the methods which have this sugar is hardcoded in the parser. So it can't be _any_ non-alphanumeric name. – Sergio Tulentsev Jul 30 '17 at 18:28
  • Might be worth noting that the result can also differ when changing the argument (instead of the receiver), e.g. `[1,2,3] * ","` returns the string `"1,2,3"` – Stefan Jul 31 '17 at 09:15