-
Notifications
You must be signed in to change notification settings - Fork 21
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
Type inference ignores type bounds in some occasion - propose easy fix #5298
Comments
Imported From: https://issues.scala-lang.org/browse/SI-5298?orig=1 |
@adriaanm said (edited on Jan 24, 2012 4:04:41 PM UTC): def foo[T <: Bar]: T = ...
foo // Nothing inferred, could infer Bar |
@Blaisorblade said (edited on Jan 26, 2012 4:44:04 PM UTC): def foo[A, B <: Traversable[A]](base: B) = null
foo(List(1)) which gives: <console>:9: error: inferred type arguments [Nothing,List[Int]] do not conform to method foo's type parameter bounds [A,B <: Traversable[A]]
foo(List(1))
^ there, the problem is in deducing Additionally, Scala's specification request that a constraint system be used for local type inference (Sec. 6.26.4), but according to the specification, both this example with |
@adriaanm said: |
@Blaisorblade said: |
@adriaanm said:
|
@Blaisorblade said: |
@Blaisorblade said: To wit, here's an example REPL session, with the above definitions in scope: scala> (_: Any) match {
| case View1(base) => base.map(identity)
| }
<console>:12: error: value map is not a member of Any
case View1(base) => base.map(identity)
scala> (_: Any) match {
| case ViewNoTypeAnnotWorkingInference(base) => base.map(identity)
| }
<console>:12: error: Cannot construct a collection of type That with elements of type Any based on a collection of type Any.
case ViewNoTypeAnnotWorkingInference(base) => base.map(identity)
//Slightly better, but implicit search got confused. However, base has type TraversableLike, maybe that's the problem...
scala> case class ViewNoTypeAnnotWorkingInference[T, Repr <: TraversableLike[T, Repr] with Traversable[T]](base: Repr with TraversableLike[T, Repr] with Traversable[T]) {
| def copy(base: Repr) = ViewNoTypeAnnotWorkingInference(base)
| }
defined class ViewNoTypeAnnotWorkingInference
scala> (_: Any) match {
| case ViewNoTypeAnnotWorkingInference(base) => base.map(identity)
| }
res4: Any => Traversable[Any] = <function1> Now I'm going to clutter my case classes definitions with this trick and unclutter my pattern matching code :-) |
@paulp said: scala> case class CC[Repr <: Traversable[_]](base: Repr) { def copy(base: Repr) = CC(base) }
defined class CC
scala> CC(List(1,2,3))
res0: CC[List[Int]] = CC(List(1, 2, 3))
scala> res0.copy(Set(1,2,3))
<console>:11: error: type mismatch;
found : scala.collection.immutable.Set[Int]
required: List[Int]
res0.copy(Set(1,2,3))
^
scala> CC(Map(1 -> 2))
res2: CC[scala.collection.immutable.Map[Int,Int]] = CC(Map(1 -> 2))
scala> res2.copy _
res3: scala.collection.immutable.Map[Int,Int] => CC[scala.collection.immutable.Map[Int,Int]] = <function1> |
@Blaisorblade said (edited on Jul 4, 2012 1:45:47 PM UTC): Why do I need [Some of you, say Adrian Moors, will recognize below some similarities with Lightweight Modular Staging, Delite, Scala Integrated Query and all that stuff; I have a paper draft which discusses the differences, should you care.] In my project, among tons of other things I manipulate ASTs representing programs on collection. The above code derives from the AST node representing trait Exp[+T] {
def interpret(): T
}
case class ListMapNode[T, U](coll: Exp[List[T]], mapping: Exp[T => U]) extends Exp[List[U]] {
def interpret(): List[U] = coll.interpret() map mapping.interpret()
} Note that mapping can be applied to the collection only because Writing the code above is not a problem since it is restricted to lists, but that restriction is stupid: I want to manipulate programs using any collection, I want a single node to represent case class MapNode[T, Repr <: TraversableLike[T, Repr],
U, That](base: Exp[Repr], f: Exp[T => U])
(implicit val c: CanBuildFrom[Repr, U, That]) extends Exp[That] {
def interpret() = base.interpret() map f.interpret()
def copy(base: Exp[Repr], f: Exp[T => U]) = MapNode[T, Repr, U, That](base, f)
} [The case class MapNode[Repr <: TraversableLike[_, Repr],
U, That](base: Exp[Repr], f: Exp[_ => U])
(implicit val c: CanBuildFrom[Repr, U, That]) extends Exp[That] {
def interpret() = base.interpret() map f.interpret()
} <console>:25: error: type mismatch;
found : _$2 => U where type _$2
required: Any => ?
def interpret() = base.interpret() map f.interpret()
^ If I now try to write type-safe transformations on these ASTs, this bug gets even more annoying, but I won't go into the details. I'll just say that it's because of those details that my code is even crazier. |
@paulp said: I recently managed to hack my way to seeing Coll inferred correctly here: def f[A, CC[X] <: Traversable[X], Coll <: CC[A]](xs: Coll) = xs I have to get back to that code, it's in a tributary somewhere. |
@Blaisorblade said: Your solution looks interesting - I thought for a second that it would work on vanilla Scala, but it doesn't (not on 2.9.2, nor on the latest 2.10 snapshot I have, from mid-June): scala> def f[A, CC[X] <: Traversable[X], Coll <: CC[A]](xs: Coll) = xs
f: [A, CC[X] <: Traversable[X], Coll <: CC[A]](xs: Coll)Coll
scala> f(List(1))
<console>:9: error: inferred kinds of the type arguments (Nothing,List[Int],List[Int]) do not conform to the expected kinds of the type parameters (type A,type CC,type Coll).
List[Int]'s type parameters do not match type CC's expected parameters: class List has one type parameter, but type CC has one
f(List(1))
^ If you have a Scalac compiler supporting such crazy code, I'd be delighted to try it out :-). The error message above also seems related to #5075/#2712. def f[A, CC[X] <: Traversable[X], Coll <: CC[A], That](xs: Coll)(implicit cbf: CanBuildFrom[Coll, A, That]): That = xs map (x => x) Unless you have |
@paulp said: |
Seems unlikely to progress in Scalac (Adriaan already said as much), the sample is currently illegal in Dotty because of the overriding/shadowing of |
I think closing issues like this is premature. I think we can make progress on inference bugs in Scala 2. |
@milessabin Paolo wrote, "the sample is currently illegal in Dotty because of the overriding/shadowing of copy methods" — can you address that? what is the path forward here that you see? |
That comment of mine was honestly lazy, the other examples in this ticket don’t depend on copy; |
More specifically: according to Martin's explanation of interpolation, Dotty's inference will (at some "strictness point") loop over uninstantiated variables and instantiate them to some bound, usually trying to infer the most specific type for a method (though solutions must be heuristic here, as long as type inference stays local rather than global). So IIUC for def foo[T <: Bar]: T = ...
foo // Nothing inferred, could infer Bar
def bar[T <: Bar](x: T): Any = ...
bar _ //infers T = Bar to minimize the return type On my other example:
I think that comment stands, and Dotty uses indeed a different algorithm — I think its subtype constraint solver can handle this well. == @milessabin I had forgot, but the real argument for closing was Adriaan's:
Here, scala/scala3#4059 exemplifies the kind of breakage you risk. We don't search for a solution over the whole search space, so inference is incomplete (and changing that still seems unfeasible, though who knows what Martin comes up with tomorrow), and sometimes (like here) it does arbitrary choices, which might trigger errors. If those choices don't match Scalac's (and they can't easily since the algorithms are so different), some existing code will stop working (while some code that currently fails might start working — but that won't affect existing and used code). |
I'm not persuaded by councils of despair. I think that, as you've observed elsewhere, the main obstacle to fixing some of these issues is deciding to fix them ... I think that applies here to. Some work here is probably relevant to this ticket too: scala/scala#6127. |
Consider the following simplified example, in particular the failure of type inference in
ViewNoTypeAnnot.copy
and the success inViewNoTypeAnnotWorkingInference.copy
:When applying
ViewNoTypeAnnot
,T
cannot be inferred because it does not appear directly in the parameter list. Therefore,scalac
infersT = Nothing
, incorrectly, andRepr = Repr@(outer environment)
, correctly, and complains because type bounds are not satisfied.However, the bounds do provide sufficient information to deduce
T = T@(outer environment)
. InViewNoTypeAnnotWorkingInference
, I intersectedRepr
with its upper bound, which should not make any difference; however, sincescalac
is happier inferring parameters which do appear in the signature, type inference now succeeds.I propose to integrate into
scalac
the transformation which I performed manually, for the purpose of helping type inference; the resulting types can be discarded after inference, but I'd guess that erasure would remove the "extra information" from bytecode easily - and at leastjavap
confirms this for the classes considered. I tried this out in a couple of other more complex examples, and the trick seems fairly reliable. The trick cannot be extended to lower bounds.In particular, it helped in this slightly more complex example, where the type appears as a parameter of
Exp
:And this also helped with real code I was writing, but which is too complex to post here. All the code above was indeed compiled (assuming I didn't mess up the copy-n-paste).
The text was updated successfully, but these errors were encountered: