Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type Boundary "Stickyness" on Wildcards

Tags:

scala

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 :)

Explanation?

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[_])

Workaround

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 :)

like image 996
Bruno Bieth Avatar asked Aug 17 '12 19:08

Bruno Bieth


1 Answers

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.

like image 173
Travis Brown Avatar answered Oct 12 '22 09:10

Travis Brown