Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Basic type inferral

Tags:

types

scala

Please consider case class Foo[A, B <: List[A]](l: B) { ... } or something akin. In particular, A as well as B need to be available somewhere in the body of Foo.

Is it possible for the compiler to infer A automatically? For example, Foo(List(1,2,3)) fails as the type checker infers A as Nothing. Perhaps there is a way by using type members to solve this problem?

I have that certain feeling that I'm overlooking something embarassingly simple here ;)

EDIT: I just found out that using another type parameter X works just fine, but atm I don't understand why that is so:

scala> case class Bar[A, B[X] <: List[X]](l: B[A])
defined class Bar

scala> Bar(List(1,2,3))
res11: Bar[Int,List] = Bar(List(1, 2, 3))

Can someone please explain this to me? Is this a unification issue?

EDIT 2: Using [A, B[X] <: List[X]](l: B[A]) can have undesired implications for certain hierarchies (although it's not really a big deal). More interestingly, I just stumbled across a blog post by Josh Suereth that implicitly shows that [A, B <: List[A]](l: B with List[A]) works just as well... No need for implicits etc.

like image 311
fotNelton Avatar asked Jan 05 '12 19:01

fotNelton


2 Answers

It doesn't really answer the "why" part, sorry, but here are some more tricks you can play. First, since you're not using the X in your example, you can write:

case class Bar[A,B[_] <: Seq[_]](l : B[A])

and then:

scala> Bar(List(1,2,3))
resN: Bar[Int,List] = Bar(List(1, 2, 3))

(I'm using the covariant Seq instead of List to show it works for subtypes as well. Also, note that this is not equivalent to using the extra X, see the comments.) Unfortunately, everytime you want to use the sequence type, you need to write B[A], i.e. instantiate it manually. One way to work around that is to write instead:

case class Bar[A,B](l : B)(implicit ev : B <:< Seq[A])

In action:

scala> Bar(List(1,2,3))
resN: Bar[Int,List[Int]] = Bar(List(1, 2, 3))

...and you get the type parameters A and B instantiated just like you always knew they should.

like image 50
Philippe Avatar answered Oct 20 '22 00:10

Philippe


It is not possible for the compiler to infer A automatically. But if it were possible it would have to say that A should be a supertype of Int, so Int or Any, not just Int!. Because a List[Int] <: List[Any]. So if the compiler would infer Int for A it would be too restrictive.

In other words:

  1. case class Foo[A, B <: List[A]](l: B) { ... } and then calling it as Foo(List(1,2,3)) you say that A must be a type for which holds that a list of it should be a supertype of a list of Int. So effectively A must be a supertype of Int because List is covariant.

  2. case class Bar[A, B[X] <: List[X]](l: B[A]) and then calling it as Foo(List(1,2,3)) you say that A must be an Int.

Case (2) leaves no room for A to be anything else than Int. Case (1) however leaves room for A to be something else than Int, e.g. it could be Any. You can see this because you could call it by Foo[Any, List[Int]](List(1,2,3)). You wouldn't be able to do this in case (2).

So the two cases are not equivalent.

like image 21
Jan van der Vorst Avatar answered Oct 19 '22 22:10

Jan van der Vorst