4

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.

println(addTwoThings("1",2))

2 Answers2

5

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 are 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

Experiments:

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

Experiments:

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) = ""

Experiments:

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

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

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

Community
  • 1
  • 1
dk14
  • 22,206
  • 4
  • 51
  • 88
  • Thanks dk14! Is there anyway to make it explicitly require S be the exact same type as opposed to having a common super type ? – NoviceHead88 Oct 15 '16 at 07:31
  • @NoviceHead88 yes, I've added two approaches to the answer: evidences and currying – dk14 Oct 15 '16 at 07:34
  • @NoviceHead88 I explained 3 possible solutions. Whichever to pick depends on your exact requirements, as each of them has its own pros and cons – dk14 Oct 15 '16 at 07:43
  • Awesome thx dk14! I think I'll be going with approach #2 :) as I might need to add more arguments and probably defining multiple type parameters . – NoviceHead88 Oct 15 '16 at 07:49
  • (Continuation of above) Might start getting unwieldy especially since I just want to ensure all arguments are the exact same type. – NoviceHead88 Oct 15 '16 at 07:50
  • I did notice you added a != AnyRef as well. Can you describe a case where just doing Any but not AnyRef might cause issues ? Thanks ! – NoviceHead88 Oct 15 '16 at 07:51
  • @NoviceHead88 the approach #2 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)` – dk14 Oct 15 '16 at 08:03
  • `Any` and `AnyRef` are the only types that can merge unrelated (independently created) type hierarchies – dk14 Oct 15 '16 at 08:05
0

Once again this is the problem of type inference of Scala Compiler

You have to give explicit type to guide the compiler

addTwoThings[String]("1",2)

The above will give compilation error.

The reason why your code works

The common super type of String and Int is Any. So the Scala compiler is assuming S in the function to Any

scala> def addTwoThings[S](item1:S, item2:S) =
     |   {
     |     item1 + " | " + item2
     |   }
addTwoThings: [S](item1: S, item2: S)String

scala> println(addTwoThings("1",2))
1 | 2

scala> println(addTwoThings[String]("1",2))
<console>:22: error: type mismatch;
 found   : Int(2)
 required: String
       println(addTwoThings[String]("1",2))
Nagarjuna Pamu
  • 14,737
  • 3
  • 22
  • 40
  • Thanks pamu ! In this case, aside from specifying the type expliclty when calling the method like you ahve done with addTwoThings[String], is there any other way to enforce this in definition of the method itself ? – NoviceHead88 Oct 15 '16 at 07:32