erip erip - 28 days ago 14
Scala Question

Why can't I call a method with parameter `this` in a class extending a generic trait?

I'm writing a trait to describe node-like objects in a Hierarchical structure (like a graph). My working code is below:

trait Hierarchical[T <: Hierarchical[_]] {
// The parents will be a list of some arbitrary Hierarchical
val parents: List[Hierarchical[_]] = Nil
var children: List[Hierarchical[T]] = List()

def addChild(child: Hierarchical[T]): Unit = {
children ++= List(child)
}
}

abstract class NonRootNode(override val parents: List[Hierarchical[_]])
extends Hierarchical[NonRootNode] {
}

abstract class RootNode extends Hierarchical[NonRootNode] {
final override val parents = Nil
}


I have some tests which reflect this behavior:

import org.scalatest.FlatSpec

class DummyRoot extends RootNode
class DummyNonRoot(override val parents: List[DummyRoot])
extends NonRootNode(parents)

class TestUtil extends FlatSpec {

"A RootNode" should "not have parents" in {
val dummyRoot = new DummyRoot
assert(dummyRoot.parents.isEmpty)
}

"A NonRootNode" should "have parents when constructed with them" in {
val dummyRoot = new DummyRoot
assert(dummyRoot.parents.isEmpty)
val dummyNonRoot = new DummyNonRoot(List(dummyRoot))
dummyRoot.addChild(dummyNonRoot)
assert(dummyNonRoot.parents.contains(dummyRoot))
assert(dummyRoot.children.contains(dummyNonRoot))
}
}


However, I find the API to be a bit unwieldy in the second test. I shouldn't need to add a child to the root node explicitly since I've already specified the child's parents. I'd like to remove this from the public API, so my thought is to modify
NonRootNode
's constructor behavior to call this for each of the parents. Specifically, I want to write:

abstract class NonRootNode(override val parents: List[Hierarchical[_]])
extends Hierarchical[NonRootNode] {

//for each parent, add `this` to its children
parents.map{p=>p.addChild(this)}
}


However, when I add this line, I get the following error:

Error:(19, 29) type mismatch;
found : NonRootNode
required: Hierarchical[_$3]
parents.map{p=>p.addChild(this)}


I'm not entirely sure why I'm getting this compiler error. My understanding of
Hierarchical[_]
is any
Hierarchical
, but I could be mistaken. In any case, I think I'm close to my desired behavior. What am I doing wrong?

Answer

Hierarchical[_] does NOT mean "any Hierarchical". It means "a Hierarchical of some fixed type, that is unknown".

For example

def foo: Hierarchical[_] = new Hierarchical[Int] 

works: it declares a function, that will return some implementation of Hierarchical, exact type of which is not known to the caller. This is fine.

On the other hand:

def bar(h: Hierarchical[String]) = doStuff(h)
bar(foo)

Does not work: function bar wants a parameter of an exact type Hierarchical[String], and what is passed to it can not be guaranteed to have that type, it's type parameter is unknown.

In your case, .addChild is a method of Hierarchical[T] (where the value of T is unknown to you), and wants a parameter of the same type . But what you are passing to it is Hierarchical[NonRootNode]. This is illegal, because there is no way to guarantee that NonRootNode will be the same as the (unknown) T.

Comments