Reputation: 12416
Lets assume I have instance of arbitrary one-argument generic class (I'll use List
in demonstration but this can me any other generic).
I'd like to write generic function that can take instances (c
) and be able to understand what generic class (A
) and what type argument (B
) produced the class (C
) of that instance.
I've come up with something like this (body of the function is not really relevant but demonstrates that C
conforms to A[B]
):
def foo[C <: A[B], A[_], B](c: C) {
val x: A[B] = c
}
... and it compiles if you invoke it like this:
foo[List[Int], List, Int](List.empty[Int])
... but compilation fails with error if I omit explicit type arguments and rely on inference:
foo(List.empty[Int])
The error I get is:
Error:Error:line (125)inferred kinds of the type arguments (List[Int],List[Int],Nothing) do not conform to the expected kinds of the type parameters (type C,type A,type B).
List[Int]'s type parameters do not match type A's expected parameters:
class List has one type parameter, but type A has one
foo(List.empty[Int])
^
Error:Error:line (125)type mismatch;
found : List[Int]
required: C
foo(List.empty[Int])
^
As you can see Scala's type inference cannot infer the types correctly in this case (seems like it's guess is List[Int]
instead of List
for 2nd argument and Nothing
instead of Int
for 3rd).
I assume that type bounds for foo
I've come up with are not precise/correct enough, so my question is how could I implement it, so Scala could infer arguments?
Note: if it helps, the assumption that all potential generics (A
s) inherit/conform some common ancestor can be made. For example, that A
can be any collection inherited from Seq
.
Note: the example described in this question is synthetic and is a distilled part of the bigger problem I am trying to solve.
Upvotes: 3
Views: 898
Reputation: 21557
In addition to hubertp answer, you can fix you function by removing obsolete (in you example) type variable C
, e.g:
def foo[A[_], B](c: A[B]) {
val x: A[B] = c
}
In this case scalac would infer A[_]
as List
and B
as Int
.
Update (according to the comment).
If you need an evidence that C
is subtype of A[B]
, then use implicit:
def foo[A[_], B, C](c: C)(implicit ev: C <:< A[B]) = {
val x: A[B] = c
}
Then it won't compile this:
scala> foo[List, String, List[Int]](List.empty[Int])
<console>:9: error: Cannot prove that List[Int] <:< List[String].
foo[List, String, List[Int]](List.empty[Int])
Upvotes: 1
Reputation: 31
This is a known limitation of current Scala's type inference for type constructors. Defining the type of formal parameter c as C only collects type constraints for C (and indirectly to A) but not B. In other words List[Int] <: C => { List[Int] <: C <: Any, C <: A[_] <: Any }
.
There is a pretty simple translation that allows to guide type inference for such cases. In your case it is:
def foo[C[_] <: A[_], A[_], B](c: A[B]) { val x: A[B] = c }
Same semantics, just slightly different type signature.
Upvotes: 3