New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Structural type parameter bounds do not work as expected #4377
Comments
Imported From: https://issues.scala-lang.org/browse/SI-4377?orig=1 |
Eduardo Pareja Tobes (eparejatobes) said: |
That looks like the correct behaviour to me. Consider this relation: scala> implicitly[(op.Cap forSome { val op: Op }) <:< Op#Cap]
res1: op.Cap forSome { val op: Op } <:< Op#Cap = <function1>
scala> implicitly[Op#Cap <:< (op.Cap forSome { val op: Op })]
<console>:15: error: Cannot prove that Op#Cap <:< op.Cap forSome { val op: Op }.
implicitly[Op#Cap <:< (op.Cap forSome { val op: Op })] So we cannot say that scala> def f[O <: Op] = implicitly[O <:< Op { type Cap <: O#Cap }]
f: [O <: Op]=> O <:< Op{type Cap <: O#Cap}
|
@joroKr21 what you wrote is of course correct, but type projections being implemented as existentials is an implementation detail, not part of spec. You cannot argue for this being correct based on it. As a matter of fact, I suspect that implementing type projections as existentials could contradict the spec. |
For further context, this compiles: def f1[O <: Op] = implicitly[O#Cap =:= (O { type Cap = O#Cap })#Cap] |
Type projections are not implemented as existentials. Anyway let me show an example that will hopefully convince you why trait Animal {
type Food
def find(): Food = ???
def eat(food: Food): Unit = ???
}
def exchangeFood[A <: Animal](x: A, y: A): Unit = {
// This would be possible if A <:< Animal { type Food = A#Food }
val x1 = x: Animal { type Food = A#Food }
val y1 = y: Animal { type Food = A#Food }
x1.eat(y1.find())
y1.eat(x1.find())
}
class Grass
class Sheep extends Animal {
type Food = Grass
}
class Wolf extends Animal {
type Food = Sheep
}
// Whoops we made sheep cannibals, and wolfs eat grass.
exchangeFood(new Sheep, new Wolf) To understand better, read up on covariance and contravariance. |
It's true the spec doesn't go in much detail here, but the right model for a type projection About:
Let's consider To me, this looks like a bug, potentially in member subtyping (https://github.com/scala/scala/blob/1e09de17a3473efb26db535a71f9ec8b03018ac2/src/reflect/scala/reflect/internal/Types.scala#L4260-L4303), but it's not super obvious :-) I'd have to think about it some more. |
@joroKr21 sorry I didn't pay enough attention to what the issue says. What I think is reasonable to expect is that given // these two compile
def f1(x: Op) = implicitly[x.type { type Cap = x.Cap } <:< x.type ]
def f2(x: Op) = implicitly[x.type <:< x.type { type Cap = x.Cap }]
// this one compiles too (of course)
def f3[O <: Op] = implicitly[O { type Cap = O#Cap } <:< O]
// this one not
// def f4[O <: Op] = implicitly[O <:< O { type Cap = O#Cap }] I don't expect anyone to agree with me here, but I'd rather live with the unsoundness and be able to use projections, or do the same as with covariant type parameters: forbid methods with arguments in contravariant position (that's exactly what happens in the classical Animals and Food example). @adriaanm thanks for taking a look at this. |
I'm going to go with this being a (tricky) bug. It compiles when we use a weaker prefix for the type on the left in -|| specializesSym(tp.narrow, member, sym.owner.thisType, sym, depth)
+|| specializesSym(tp, member, sym.owner.thisType, sym, depth) That implies narrowing of a skolemized type is losing information. Since narrowing is "Map to a singleton type which is a subtype of this type.", it should hold that |
We all know by now that general type projection is unsound. However I don't see any bad bounds in the animal example. The problem would arise exactly from having two different instances of @adriaanm The problem isn't with @eparejatobes Living with unsoundness is not the solution IMHO, especially given the flexibility of Scala. Usually you can redesign your program to pass around an instance of |
or, without implicits
But the following actually works!
Tested on 2.8.1 and 2.10.0.r24524-b20110321020039
See https://groups.google.com/d/msg/scala-user/WIjt66A74Kk/CFQUf1NyD3oJ for another example.
The text was updated successfully, but these errors were encountered: