The error in Test.test seems unjustified:
sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]
case class Test[U]() {
def test[V](t: A[Option[U], V]) = t match {
case B() => null // constructor cannot be instantiated to expected type; found : B[V] required: A[Option[U],?V1] where type ?V1 <: V (this is a GADT skolem)
}
def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}
object Test2 {
def test2[U, V](t: A[Option[U], V]) = t match {
case B() => null // This works
}
}
There are a couple ways to make the error change, or go away:
If we remove the V parameter on trait A (and case class B), the 'GADT-skolem' part of the error goes away but the 'constructor cannot be instantiated' part remains.
If we move the U parameter of the Test class to the Test.test method, the error goes away. Why ? (Similarly, the error is not present in Test2.test2)
The following link also identifies that problem, but I do not understand the provided explanation. http://lambdalog.seanseefried.com/tags/GADTs.html
Is this an error in the compiler ? (2.10.2-RC2)
Thank you for any help with clarifying that.
2014/08/05: I have managed to further simplify the code, and provide another example where U is bound outside the immediate function without causing a compilation error. I still observe this error in 2.11.2.
sealed trait A[U]
case class B() extends A[Unit]
case class Test[U]() {
def test(t: A[U]) = t match {
case B() => ??? // constructor cannot be instantiated to expected type; found : B required: A[U]
}
}
object Test2 {
def test2[U](t: A[U]) = t match {
case B() => ??? // This works
}
def test3[U] = {
def test(t: A[U]) = t match {
case B() => ??? // This works
}
}
}
Simplified like that this looks more like a compiler bug or limitation. Or am I missing something ?
Constructor patterns must conform to the expected type of the pattern, which means B <: A[U], a claim which is true if U is a type parameter of the method presently being called (because it can be instantiated to the appropriate type argument) but untrue if U is a previously bound class type parameter.
You can certainly question the value of the "must conform" rule. I know I have. We generally evade this error by upcasting the scrutinee until the constructor pattern conforms. Specifically,
Addendum: in a comment the questioner says "The question is simple on why it doesn't works with type parameter at class level but works with at method level." Instantiated class type parameters are visible externally and have an indefinite lifetime, neither of which is true for instantiated method type parameters. This has implications for type soundness. Given Foo[A], once I'm in possession of a Foo[Int] then A must be Int when referring to that instance, always and forever.
In principle you could treat class type parameters similarly inside a constructor call, because the type parameter is still unbound and can be inferred opportunistically. That's it, though. Once it's out there in the world, there's no room to renegotiate.
One more addendum: I see people doing this a lot, taking as a premise that the compiler is a paragon of correctness and all that's left for us to do is ponder its output until our understanding has evolved far enough to match it. Such mental gymnastics have taken place under that tent, we could staff Cirque de Soleil a couple times over.
That's the current version of scala - this is still what it does. No warnings. So maybe ask yourselves whether it's wise to be offering the presumption of correctness to a language and implementation which are so trivially unsound after more than ten years of existence.
Edit: this is not an answer to the question. I'm keeping it for reference (and the comments).
The link you provided already gives the answer.
In the case of the parameterised method,
U
is infered from the argument of the actual method call. So, the fact that the caseB()
was chosen implies thatU >: Unit
(otherwise the method could not have been called with aB
) and the compiler is happy.In the case of the parameterized class,
U
is independent from the method argument. So, the fact that the caseB()
was chosen tells the compiler nothing aboutU
and it can not confirm thatU >: Unit
. I think if you add such a type bound toU
it should work. I haven't tried it, though.Looks like it is a compiler caveat. From this, martin odersky puts it as:
PS: Thanks to @retronym who provided the reference from discussion here
On why it throws an error in case of class:
This works:
To examplain why the compiler threw an error: Try doing this:
To understand this, from §8.1.6: above
Temp
is a polymorphic type. If the case class is polymorphic then:i.e. the compiler smartly tries to instantiate
Temp
in line-a such that it conforms to the primary constructor ofthis
( which if above compiled successfully then in case ofTemp(1)
would be something likeExp[Int]
and hence compiler instantiates Temp in line-a with parameter asInt
.Now in our case: the compiler is trying to instantiate
B
. It sees thatt
is of typeA[Option[U],V]
whereU
is already fixed and obtained from class parameter andV
is generic type of method. On trying to initializeB
it tries to create in such a way that it ultimately getsA[Option[U],V]
. So withB()
it is somehow trying to getA[Option[U],V]
. But it cant asB
isA[Option[Unit],V]
. Hence it ultimately cannot initializeB
. Fixing this makes it workIts not required in case of test-2 because: The compiler as explained in above process is trying to initialize type parameter of B. It knows t has type parameter [Option[U], V] where U and V are both generic wrt method and are obtained from argument. It tries to initialize B based on the agrument. If the argument was new B[String] it tries deriving B[String] and hence U is automatically obtained as Option[Unit]. If the argument was new A[Option[Int],String] then it obviously it wont match.
The difference has to do with what information the compiler and runtime have in each case, combined with what the restrictions on the types are. Below the ambiguity is clarified by having U be the trait and class parameter, and X be the method type paramter.
In Test2.test2, the compiler knows that an instance of A[X] will be provided, with no limits on X.It can generate code that inspects the parameter provided to see if it is B, and handle the case.
In class Test method test, the compiler knows not that some A[X] is provided when called, but that some specific type, U, is presented. This type U can be anything. However, the pattern match is on an algebraic data type. This data type has exactly one valid variation, of type A[Unit]. But this signature is for A[U] where U is not limited do Unit. This is a contradiction. The class definition says that U is a free type parameter, the method says it is Unit.
Imagine you instantiate Test:
Now, at the use site, the method is:
There is no such type A[Int]. Pattern matching on B is when the compiler inspects this condition. A[U] for any U is incompatible with the type, hence "constructor cannot be instantiated to expected type; found : B required: A[U]"
The difference with the method versus class type parameter is that one is bound while one is free, at the time the method is called in the runtime.
Another way to think about it is that defining the method
implies that U is Unit, which is inconsistent with the class declaration that U is anything.
The compiler is absolutely correct with its error message. An easy example should describe the issue well:
f
andg
have different type signatures.g
takes anA[V]
whereV
is an arbitrary type parameter. On the other hand,f
takes anA[U]
whereU
is not arbitrary but dependent from the outer classT
.The compiler does not know what type
U
can be at the moment when it typechecksT
- it needs to typecheck an instantiation ofT
to find out which types are used. One can say thatU
is a concrete type inside ofT
, but a generic type outside of it. This means it has to reject every code fragment that gets inside ofT
more concrete about the type ofU
.Calling
g
from insidef
is clearly allowed - after all an arbitrary type forV
can be chosen, which isU
in this case. BecauseU
is never again referenced ing
it doesn't matter what type it may have.Your other code example underlies the same limitations - it is just an example that is more complex.
Btw, that this code
is valid looks weird to me. It doesn't matter if
U
is bound by a class or a method - from inside of the binding scope we can't give any guarantees about its type, therefore the compiler should reject this pattern match too.