Wickoo
Wickoo

Reputation: 7345

Scala priority of method call on implicit object

Let's say I have the following scala code:

case class Term(c:Char) {
   def unary_+ = Plus(this)
}

case class Plus(t:Term)

object Term {
  implicit def fromChar(c:Char) = Term(c) 
}

Now I get this from the scala console:

scala> val p = +'a'
p: Int = 97

scala> val q:Plus = +'a'
<console>:16: error: type mismatch;
found   : Int
required: Plus
   val q:Plus = +'a'
                ^

Because '+' is already present on the Char type, the implicit conversion does not take place, I think. Is there a way to override the default behaviour and apply '+' on the converted Term before applying on the Char type?

(BTW, the example is artificial and I'm not looking for alternative designs. The example is just here to illustrate the problem)

Upvotes: 0

Views: 267

Answers (1)

R&#233;gis Jean-Gilles
R&#233;gis Jean-Gilles

Reputation: 32719

No, there is no way to override the default + operator, not even with an implicit conversion. When it encounters an operator (actually a method, as operators are just plain methods) that is not defined on the receiving object, the compiler will look for an implicit conversion to an object to does provide this operator. But if the operator is already defined on the target object, it will never look up for any conversion, the original operator will always be called. You should thus define a separate operator whose name will not conflict with any preexisting operator.

UPDATE: The precise rules that govern implicit conversions are defined in the Scala Language Specification:

Views are applied in three situations.

  1. If an expression e is of type T , and T does not conform to the expression’s expected type pt. In this case an implicit v is searched which is applicable to e and whose result type conforms to pt. The search proceeds as in the case of implicit parameters, where the implicit scope is the one of T => pt. If such a view is found, the expression e is converted to v(e).
  2. In a selection e.m with e of type T , if the selector m does not denote a member of T . In this case, a view v is searched which is applicable to e and whose result contains a member named m. The search proceeds as in the case of implicit parameters, where the implicit scope is the one of T . If such a view is found, the selection e.m is converted to v(e).m.
  3. In a selection e.m(args) with e of type T , if the selector m denotes some member(s) of T , but none of these members is applicable to the arguments args. In this case a view v is searched which is applicable to e and whose result contains a method m which is applicable to args. The search proceeds as in the case of implicit parameters, where the implicit scope is the one of T . If such a view is found, the selection e.m is converted to v(e).m(args).

In other words, an implicit conversion occurs in 3 situations:

  1. when an expression is of type T but is used in a context where the unrelated type T' is expected, an implicit conversion from T to T' (if any such conversion is in scope) is applied.

  2. when trying to access an object's member that does not exists on said object, an implicit conversion from the object into another object that does have this member (if any such conversion is in scope) is applied.

  3. when trying to call a method of an object's with a parameter list that does not match any of the corresponding overloads, the compiler applies an implicit conversion from the object into another object that does have a method of this name and with a compatible parameter list (if any such conversion is in scope).

    Note for completeness that this actually applies to more than just methods (inner objects/vals with an apply method are eligible too). Note also that this is the case that Randall Schulz was talking about in his comment below.

So in your case, points (2) and (3) are relevant. Given that you want to define a method named unary_+, which already exists for type Int, case (2) won't kick in. And given that your version has the same parameter list as the built-in Int.unary_+ method (they are both parameterless), point (3) won't kick in either. So you definitly cannot define an implicit that will redefine unary_+.

Upvotes: 5

Related Questions