Eugene Loy
Eugene Loy

Reputation: 12416

Scala: type inference of generic and it's type argument

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 (As) 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

Answers (2)

4lex1v
4lex1v

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

hubertp
hubertp

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

Related Questions