I'm creating some basic abstract data types and algorithms to brush up on my CS fundamentals, and learning Scala along the way. I'm running into trouble with my BinarySearchTree data type, which is an implementation of a more abstract BinaryTree:
abstract class BinaryTree[T](stored_value: T) {
var contents = stored_value
var l: this.type = _
var r: this.type = _
...
}
class BinarySearchTree[T <: Ordered[T]](stored_value: T) extends BinaryTree(stored_value) {
def insert(newval: T) {
if (newval <= contents) {
if (l == null) {
throw new NotDefinedError("Still trying to work around type erasure.")
} else {
l.insert(newval)
}
} else {
if (r == null) {
throw new NotDefinedError("Still trying to work around type erasure.")
} else {
r.insert(newval)
}
}
}
In the blocks that throw NotDefinedErrors, I've tried a few approaches like l = new this.type(newval)
, substituting this.type
for BinarySearchTree[T]
and anything else I can think of. Depending on how I try to express that type, I either get something like:
class type required but BinarySearchTree.this.type found
or:
type mismatch; found : trees.BinarySearchTree[T] required: BinarySearchTree.this.type
Do I need to override l and r with a different type in the definition of BinarySearchTree? Or just call a different type of constructor when I'm attaching a new value to them? Or some other option?
I agree with @dhg's recommendation that you explore immutable tree data structures as an alternative to your current direction. However if a mutable tree is really what you need then read on ...
Your immediate problem is that this.type
in the definition of BinaryTree[T]
doesn't mean what you think it means. It's actually the singleton type of the enclosing instance, ie. the type which inhabited by this
and nothing else. Here's an example illustrating that,
scala> class Foo { def self : this.type = this /* OK */ }
defined class Foo
scala> class Bar { def self : this.type = new Bar /* Does not compile */ }
<console>:7: error: type mismatch;
found : Bar
required: Bar.this.type
class Bar { def self : this.type = new Bar /* Does not compile */ }
^
This is obviously a much more specific type than the one you really want.
To fix the problem one option would be to weaken the types of l
and r
to BinaryTree[T]
, as in @dhg's answer. However, I think that what you were originally after when using this.type
was for the tree nodes to be self-typed and so refined to BinarySearchTree[T] in the subclass. You can do this as you would in Java with a recursive type bound, or you can do it with an abstract type member like so,
abstract class BinaryTree[T] {
type Self <: BinaryTree[T]
var contents : T
var l: Self = _
var r: Self = _
}
class BinarySearchTree[T <: Ordered[T]](stored_value : T) extends BinaryTree[T] {
type Self = BinarySearchTree[T]
var contents = stored_value
def insert(newval: T) {
if (newval <= contents) {
if (l == null) {
new BinarySearchTree(newval)
} else {
l.insert(newval)
}
} else {
if (r == null) {
new BinarySearchTree(newval)
} else {
r.insert(newval)
}
}
}
}