I came across this post on the scala forum.
Here is a recap:
I just realized that the Scala compiler does not seem to "carry over" type boundaries on wildcards:
scala> class Foo { def foo = 42 }
defined class Foo
scala> class Bar[A <: Foo](val a: A)
defined class Bar
scala> def bar(x: Bar[_]) = x.a.foo
:7: error: value foo is not a member of Any
I would expect that the parameter of method bar is still upper-bound by Foo, even though its exact type parameter is unimportant in the method. Is there a specific reason for this behavior?
The discussion then goes into a Spec interpretation dispute :)
Eventually the poster suggest this explanation:
However, if the compiler did this for Bar[_], for consistency reasons it would also have to do it for Bar[A]. The latter however would have some strange consequences. def bar[A](x: Bar[A], y: Bob[A]) for example would suddenly have to carry an implicit type bound for Bob. If Bob had its own type bound things would be really messy.
Which I don't get because
def bar[A](x: Bar[A])
alone wouldn't compile since Bar type parameter is bounded.
Anyway I believe the following would make total sense:
def bar(x: Bar[_], y : Bob[_])
As a workaround they suggest:
def bar(x: Bar[_ <: Foo]) = x.a.foo
Which beside not being DRY it makes things difficult:
Let's consider a Tree
abstract class Tree[T <: Tree[T]] { val subTree : List[T] }
Now how would you define functions (defined outside of the Tree class obviously) that are going through the tree recursively:
def size( tree : Tree[_] ) = tree.subTree.size + tree.subTree.map(size(_)).sum
obviously won't work, since subTree will be turned into a List[Any], so we need a type parameter:
def size[T <: Tree[T]]( tree : T ) = ...
Even uglier :
class OwnATree( val myTree : Tree[_] ){}
Should become
class OwnATree[T <: Tree[T]]( val myTree : T ){}
etc, etc ...
I believe there's something wrong somewhere :)
The simplest way to accomplish what you want with size
and OwnATree
is to use an existential type:
def size(tree: Tree[T] forSome { type T <: Tree[T] }): Int =
tree.subTree.size + tree.subTree.map(size(_)).sum
And:
class OwnATree(val myTree: Tree[T] forSome { type T <: Tree[T] })
Your Tree[_]
versions are actually also using existential types—they're just wrapped up in some syntactic sugar. In other words,
def size(tree: Tree[_]): Int = ...
Is just syntactic sugar for:
def size(tree: Tree[T] forSome { type T }): Int = ...
You can't add the constraint you need to the underscore version, but you can to the desugared one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With