This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Re: 2.8 migration guide needed

2 replies
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.

[Since you guys mostly spent the rest of that thread talking about
implicits, I feel like I should elaborate on my october findings: either
to find out my analysis is wrong, or to make sure everyone knows that
implicits are only half the problem.]

I should have posted an example, but I meant in my last message in that
thread that implicits turned out to be a red herring. I turned off
implicit conversions when typing a match and all the examples still type
check. The reason is illustrated here:

trait A
trait B

object A {
def unapplySeq(x: A) = Some(Seq(1,2))
}

def f(x: B) = x match { case A(b, c) => b ; case _ => -1 }

The compiler has to assume any scrutinee which could conceivably match
the static type of the unapply argument could match. So from the
standpoint of the typer, if you have an "A" and some method takes a "B",
you can never say statically that the "A" can't call the method, because
it can always be an "A with B".

And unfortunately even if "A with B" is otherwise impossible, it is
(usually) inhabited by null. It is only the special semantics of the
pattern matcher which allow us to say it's not null.

scala> var x: Array[Int] with Seq[Int] = null
x: Array[Int] with Seq[Int] = null

scala> var x: String with Seq[Int] = null
x: String with Seq[Int] = null

scala> var x: Int with String = _
x: Int with String = 0

So to rule out an extractor statically on the basis of types, we have to
introduce a bunch of new knowledge to the typer: that the scrutinee is
effectively of type "X with NotNull" except in a constant null or
default pattern; and the conditions under which an intersection type is
inhabited only by null (A and B both classes with neither inheriting
from the other, A or B final and not including the other, ... ?) And
would that knowledge be used outside of patterns? I don't know the
implications of that.

I'm in full agreement that this is an ugly trap that we need to
ameliorate, but I doubt we can do any more than warn at this point.
Even if we address the intersection types somehow, then the implicit
conversions do become relevant: one can of worms begets another.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: 2.8 migration guide needed

Of course after writing that I see that some of that knowledge exists,
for instance in isPopulated.

def check(tp1: Type, tp2: Type) =
if (tp1.typeSymbol.isClass && tp1.typeSymbol.hasFlag(FINAL))
tp1 <:< tp2 || isNumericValueClass(tp1.typeSymbol) && isNumericValueClass(tp2.typeSymbol)
else tp1.baseClasses forall (bc =>
tp2.baseTypeIndex(bc) < 0 || isConsistent(tp1.baseType(bc), tp2.baseType(bc)))

I feel like there was some additional factor which made this more
difficult to resolve in extractors than in typed patterns, but now I
don't remember what it was. And I don't see any mention of null in that
function. But maybe it's just a bug that some of those things type
checked without implicits in place.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: 2.8 migration guide needed

OK! If I disable implicit search when typing a pattern, and comment back
in a commented out line, we start seeing behavior we like:

scala> "12" match { case Seq(1, 2) => true }
:5: error: pattern type is incompatible with expected type;
found : Seq[A]
required: java.lang.String
"12" match { case Seq(1, 2) => true }
^

scala> Array(1,2) match { case Seq(1, 2) => true }
:5: error: pattern type is incompatible with expected type;
found : Seq[A]
required: Array[Int]
Array(1,2) match { case Seq(1, 2) => true }
^
Here is the line of interest, the end of isPopulated, with comment
verbatim. No implicits + symmetric check == lots more ruled out.

check(tp1, tp2)/* && check(tp2, tp1)*/
// need to investgate why this can't be made symmetric --
// neg/gadts1 fails, and run/existials also.

Here is the relevant part of neg/gadts1. It's supposed to not compile,
but does after that change.

trait Term[+a]
case class Cell[a](var x: a) extends Term[a]
case class NumTerm(val n: Number) extends Term[Number]

def f[a](t:Term[a], c:Cell[a]): Unit =
t match {
case NumTerm(n) => c.x = Double(1.0)
}

It looks like some type parameter constraints are lost.

Here is run/existentials:

trait Counter[T] {
def newCounter: T
def get(i: T): Int
def inc(i: T): T
}

def foo(x : Counter[T] { def name : String } forSome { type T }) = x match {
case ctr: Counter[t] =>
val c = ctr.newCounter
println(ctr.name+" "+ctr.get(ctr.inc(ctr.inc(c))))
case _ =>
}

It goes from compiling to not compiling over a related issue:

files/run/existentials.scala:60: error: cyclic aliasing or subtyping involving type T
val c = ctr.newCounter
^

To me it looks like before isPopulated is called, some setup takes place
settings up fresh type vars and such, and maybe that setup is being
reused on the second check when it should be set up again. But that's a
totally wild guess at this point.

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland