Should the following be compiled without needing an explicit type definition on this
?
def prepList[B >: A](prefix: PlayList[B]) : PlayList[B] =
prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))
It seems to me that the type should be able to inferred. Is this just a limitation in the Scala compiler, or is there a type-theoretic reason that this cannot be done? I don't really have a feel yet for what the Scala type inferencer can be expected to handle.
Working through that method:
B >: A
by definitionthis
has typePlayList[A]
, which is a subtype ofPlayList[B]
sinceB >: A
and PlayList is covariant inA
.node
has typeB
, the parameter type ofprefix
.- second parameter to function parameter
f
infoldr
has the same type (declaredB
) as the first parameter tofoldr
. - Thus
suffix
has the same type asthis
, so in particular it is aPlayList[A]
. SinceB >: A
,suffix.prepNode()
takes aB
.
I would like the compiler to see that suffix.prepNode(node)
is legal where node
has type B
. It appears to be able to do this only if I explicitly specify a type on the invocation of foldr
or on the reference to this
in that invocation.
Interestingly, if I specify explicit types on the function parameters as (node: B, suffix: PlayList[B])
, a type mismatch error is still generated on the parameter to the method call suffix.prepNode(node)
: "found: B, required: A"
I'm using Scala 2.8 RC6. Full example below, the line in question is line 8.
sealed abstract class PlayList[+A] {
import PlayList._
def foldr[B](b: B)(f: (A, B) => B): B
def prepNode[B >: A](b: B): PlayList[B] = nel(b, this)
def prepList[B >: A](prefix: PlayList[B]): PlayList[B] =
// need to specify type here explicitly
prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node))
override def toString = foldr("")((node, string) => node + "::" + string)
}
object PlayList {
def nil[A]: PlayList[A] = Nil
def nel[A](head: A, tail: PlayList[A]): PlayList[A] = Nel(head, tail)
def nel[A](as: A*): PlayList[A] = as.foldRight(nil[A])((a, l) => l.prepNode(a))
}
case object Nil extends PlayList[Nothing] {
def foldr[B](b: B)(f: (Nothing, B) => B) = b
}
case class Nel[+A](head: A, tail: PlayList[A]) extends PlayList[A] {
def foldr[B](b: B)(f: (A, B) => B) = f(head, tail.foldr(b)(f))
}
EDIT: second attempt to reason through the compilation steps
- Renaming for clarity,
foldr
takes parameters of types(T)((U, T) => T)
. We're trying to infer the values of typesU
andT
. - There is a relationship between the first parameter to
foldr
and the second parameter to the function - they're the same thing,T
. (In partial answer to Daniel.) - The types of the objects we're passing as those parameters are
this: PlayList[A]
andsuffix: PlayList[B]
- So, since
B >: A
, the most specific common super type isPlayList[B]
; therefore we haveT == PlayList[B]
. Note that we don't need any relationship betweenU
andT
to deduce this.
This is where I get stuck:
- From the compile error message, the inferencer clearly thinks that
node
has typeB
(that is,U == B
). - I can't see how it gets to the conclusion that
U == B
without inferring it from the type parameter ofsuffix
. (Can the scala compiler do this?) - If that step of inference is what happens, then it follows that
U == B
, and we've compiled successfully. So which step went wrong?
EDIT 2: In renaming the foldr
parameter types above I missed that U == A
by definition, it's the type parameter of the PlayList
class. I think this is still consistent with the above steps though, since we're calling it on an instance of PlayList[B]
.
So at the call site, T == PlayList[B]
as the least common super-type of a couple of things, and U == B
by definition of foldr
on the receiver. That seems concise enough to narrow down to a couple of options:
- the compiler can't resolve those multiple types and compute the upper bound of
B
- bug in getting from return type
PlayList[B]
offoldr
to type of parameter ofprepNode
(skeptical)