NoviceHead88 NoviceHead88 - 1 year ago 61
Scala Question

Polymorphic Methods in Scala- why is this allowed?

I have the following polymorphic method in scala:

def addTwoThings[S](item1:S, item2:S) =
item1 + " | " + item2

The following however compiles fine, even though I've specified that item1 and item2 should be the same type "S". Do I need to do something with implicit evidence ?

To be clear, I actually want the compiler to complain that they're not the same type, but it seems to be allowing me to proceed, which is what's confusing me. Thanks.


Answer Source

The reason you get + operator is working for you without using .toString explicitly is described here: What Scala feature allows the plus operator to be used on Any?. Those additional implicits in Predef are source of many problems in scala, but it's hard to get rid of such legacy.

To find out why addTwoThings("1",2) is working - let's rewrite it to get exact inference for S:

scala> def addTwoThings[S](item1:S, item2:S): S = item1
addTwoThings: [S](item1: S, item2: S)S

scala> addTwoThings(1, "1")
res5: Any = 1

You can notice that type S = Any was inferred as a common type.

So, here is several solutions:

1) If you can allow two type-parameters in your method's signature, here is the solution:

def addTwoThings[S1, S2](item1:S1, item2:S2)(implicit ev: S1 =:= S2, ev2: S2 =:= S1) = {
    item1 + " | " + item2

Note: ev2 might be redundant for check, but it provides more complete equality, see Scala: generic method using implicit evidence doesn't compile


scala> addTwoThings(1, "1")
<console>:18: error: Cannot prove that Int =:= String.
              addTwoThings(1, "1")

scala> addTwoThings("2", "1")
res11: String = 2 | 1

2) Or you can exclude Any/AnyRef (common supertype for everything) using Evidence that types are not equal in Scala :

trait =:!=[A, B]
implicit def neq[A, B] : A =:!= B = new =:!=[A, B] {}
implicit def neqAmbig1[A] : A =:!= A = ???
implicit def neqAmbig2[A] : A =:!= A = ???

def addTwoThings[S](item1:S, item2:S)(implicit ev: S =:!= Any, ev2: S =:!= AnyRef): S = item1


scala> addTwoThings(1, "1")
<console>:18: error: ambiguous implicit values:
 both method neqAmbig1 of type [A]=> =:!=[A,A]
 and method neqAmbig2 of type [A]=> =:!=[A,A]
 match expected type =:!=[Any,Any]
              addTwoThings(1, "1")

scala> addTwoThings(1, 1)
res7: Int = 1

Note: This approach doesn't require exact type equality, so if B1 <: B2 - addTwoThings(b1, b2) - will still work. It protects you only from unrelated type hierarchies (which might be useful). In practice != Any without != AnyRef will not give you error on object A; object B; addTwoThings(B, A).

Note2: The error provided by compiler is hardly readable, more info here - How can I customize Scala ambiguous implicit errors when using shapeless type inequalities

3) Another approach is currying:

def addTwoThings[S](item1:S)(item2:S) = ""


scala> addTwoThings(1)(1)
res8: String = ""

scala> addTwoThings(1)("1")
<console>:18: error: type mismatch;
 found   : String("1")
 required: Int

Type inference will not look for common supertype of curried parameters (you can read more here)