// Start writing your ScalaFiddle code here
sealed trait DSL[A]{
// def run(): A ={
// this match {
// case GetLength(something) =>
// something.length
// case ShowResult(number) =>
// s"the length is $number"
// }
// }
}
case class GetLength(something: String) extends DSL[Int]
case class ShowResult(number: Int) extends DSL[String]
def run[A](fa:DSL[A]): A ={
fa match {
case GetLength(something) =>
something.length
case ShowResult(number) =>
s"the length is $number"
}
}
val dslGetLength = GetLength("123456789")
val length = run(dslGetLength)
val dslShowResult = ShowResult(length)
println(run(dslShowResult))
// print: the length is 9
scalafiddle here
run
function not compile in the DSL[A]
trait, but worked outside?This is a case of generalized abstract data type.
When you have a DSL[A]
and function returning A
, compiler can prove that:
case GetLength
A=Int
so you can return Int
therecase ShowResult
A=String
so you can return String
however, Scala 2 is known to not have a perfect support of GADTs, so sometimes compiler fails, even if it should work. I guess some compiler dev could figure out the exact case, but, interestingly, it can be worked around with:
sealed trait DSL[A]{
def run(): A = DSL.run(this)
}
object DSL {
def run[A](fa:DSL[A]): A ={
fa match {
case GetLength(something) =>
something.length
case ShowResult(number) =>
s"the length is $number"
}
}
}
case class GetLength(something: String) extends DSL[Int]
case class ShowResult(number: Int) extends DSL[String]
My wild guess would be that pattern matching in a generic method is kind of a special case in a compiler, which is not triggered when A
is fixed. I think that, because the following code also works:
sealed trait DSL[A]{
def run(): A = runMe(this)
private def runMe[B](dsl: DSL[B]): B = {
dsl match {
case GetLength(something) =>
something.length
case ShowResult(number) =>
s"the length is $number"
}
}
}
whereas this fails as well:
sealed trait DSL[A]{
def run(): A = {
val fa: DSL[A] = this // make sure error is not related to special treatment of "this", this.type, etc
fa match {
case GetLength(something) =>
something.length
case ShowResult(number) =>
s"the length is $number"
}
}
}
cmd4.sc:5: constructor cannot be instantiated to expected type;
found : ammonite.$sess.cmd4.GetLength
required: ammonite.$sess.cmd4.DSL[A]
case GetLength(something) =>
^
cmd4.sc:7: constructor cannot be instantiated to expected type;
found : ammonite.$sess.cmd4.ShowResult
required: ammonite.$sess.cmd4.DSL[A]
case ShowResult(number) =>
^
Compilation Failed
In other words, I suspect that type parameter change how things are being evaluated:
def runMe[B](dsl: DSL[B]): B
has a type parameter, so results of each case
inside match
are compared against B
where for each case value of B
can be proven to be some specific type (Int, String)def run: A
however compiler is somehow prevented from making such analysis - IMHO it is a bug, but perhaps it is a result of some obscure feature.From what I see the same error occurs in Dotty, so it is either duplicated bug or a limitation of a type-level checker (after all GADT aren't widely use din Scala, yet) - I would suggest reporting issue to Scala/Dotty team and letting them decide what it is.
Pattern matching seems to work differently depending on if type parameter comes from the enclosing method versus enclosing class. Here is a simplified example where using class type parameter A
trait Base[T]
case class Derived(v: Int) extends Base[Int]
class Test[A] {
def method(arg: Base[A]) = {
arg match {
case Derived(_) => 42
}
}
}
raises error
Error:(7, 12) constructor cannot be instantiated to expected type;
found : A$A87.this.Derived
required: A$A87.this.Base[A]
case Derived(_) => 42
^
whilst it works using method type parameter A
class Test {
def method[A](arg: Base[A]) = {
arg match {
case Derived(_) => 42
}
}
}
SLS 8.4: Pattern Matching Expressions seems to explain what happens in the method scenario
Let π be the type of the selector expression π and let π1,β¦,ππ be the type parameters of all methods enclosing the pattern matching expression. For every ππ, let πΏπ be its lower bound and ππ be its higher bound. Every pattern πβπ1,,β¦,ππ can be typed in two ways. First, it is attempted to type π with π as its expected type. If this fails, π is instead typed with a modified expected type πβ² which results from π by replacing every occurrence of a type parameter ππ by undefined.
AFAIU, we have
e = arg
a1 = A
T = Base[A]
p1 = Derived(_)
First, it attempts to type π with π as its expected type, however Derived
does not conform to Base[A]
. Thus it attempts the second rule
If this fails, π is instead typed with a modified expected type πβ² which results from π by replacing every occurrence of a type parameter ππ by undefined.
Assuming undefined means something like existential type, then we have T' = Base[_]
, and because the following indeed holds
implicitly[Derived <:< Base[_]]
then pattern matching in the case of method type parameter becomes something like
class Test {
def method[A](arg: Base[A]) = {
(arg: Base[_]) match {
case Derived(_) => 42
}
}
}
which indeed compiles. This seems to be confirmed by making the class type parameter case successfully compile like so
class Test[A] {
def method(arg: Base[A]) = {
(arg: Base[_]) match {
case Derived(_) => 42
}
}
}
Therefore it seems the second rule is not attempted in type parameter inference for constructor patterns when type parameter comes from enclosing class.
At least these seems to be some of the moving pieces which hopefully someone with actual knowledge can assemble into coherent explanation, as I am mostly guessing.
run
returns a generic A
, but this only works in the case you didn't comment because you're accepting A
as a type parameter (which the compiler can figure out).
The following would work instead:
sealed trait DSL[A] {
def run(): A
}
case class GetLength(something: String) extends DSL[Int] {
def run(): Int = something.length
}
case class ShowResult(number: Int) extends DSL[String] {
def run(): String = s"the length is $number"
}
val dslGetLength = GetLength("123456789")
val length = dslGetLength.run()
val dslShowResult = ShowResult(length)
println(dslShowResult.run())
You can play around with this code here on Scastie or alternatively on Scala Fiddle.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With