It does not compile, because as soon as you write Board[_]
, the compiler does not infer anything useful about the anonymous type parameter _
.
There are several work-arounds (which are not the same as you already proposed):
- Use
Board[X] forSome { type X <: Board[X] }
- Use pattern matching to infer more information about the type
Using forSome
existential quantification
This can be easily fixed with a forSome
-existential quantification:
import scala.language.existentials
object BoardOps_forSome {
def updated(board: Board[X] forSome { type X <: Board[X] }) = {
board.updated.updated.updated
}
}
This allows you to invoke updated
an unlimited number of times.
Using pattern matching
You could actually work around it without changing the signature, using pattern matching. For example, this ungodly construction allows you to apply the method updated
three times (works unlimited number of times):
object BoardOps_patternMatch {
def updated(board: Board[_]) = {
board match {
case b: Board[x] => b.updated match {
case c: Board[y] => c.updated match {
case d: Board[z] => d.updated
}
}
}
}
}
This is because as soon as you bind the unknown type to type variables x
, y
, z
, the compiler is forced to do some extra inference work, and infers that it must actually be x <: Board[_$?]
etc. Unfortunately, it does the inference only one step at a time, because if it tried to compute the most precise type, the type computation would diverge.
Upper bounds and universal quantification are not the same
Notice that your first workaround works only twice:
object BoardOps_upperBound_once {
def updated(board: Board[_<:Board[_]]) = {
board.updated.updated // third .updated would not work
}
}
Therefore, it is not equivalent to your second workaround, which also works an unlimited number of times, here example with three invokations of updated
:
object BoardOps_genericT {
def updated[T <: Board[T]](board: T) : T = {
board.updated.updated.updated
}
}