danio
danio

Reputation: 8653

How can I create a new specific type of class when extending a trait in scala

I have a trait in which (among other things) I want a method that will create a new instance of the class, and then there are other methods that use that instance of the class.

A very cut down version of my code is:

trait A {
  def prev: A
  def get(end: A): A
}

class B extends A {
  def prev: B = new B()
  def get(end: B): B = end.prev
}

What I am trying to show here is that next will return a new instance of the class (in reality with some new constructor parameters) and that the get method will make use of next internally (along with other logic)

The problem with the above is that the compiler says "class B must implement abstract member get(end: A): A", which is reasonable.

I tried to solve it using type bounds as:

trait A {
  def prev: A
  def get(end: A): A
}

case class B extends A {
  def prev[TX <: A]: TX = new B()
  def get[TX <: A](end: TX): TX = end.prev
}

but now the error is "Expression of type B doesn't conform to expected type TX" on new B() and "Expression of type A doesn't conform to expected type TX" on end.prev

I don't understand why this is a problem as next is returning a B which is a subtype of A, which is what TX is.

Is there a way to implement what I wish to do here?


A bit of context in case the above all seems too abstract. I am implementing a circular doubly linked list as there's nothing like that that I could find. The trait includes:

trait Circular[T] {
  // Nodes in the list from the current position up to but NOT INCLUDING the end
  def toStream(end: Circular[T]): Stream[Circular[T]]
  def prev: Circular[T]
  ...

And my class looks like:

case class Node[T](val data: T, var prev: Node[T], var next: Node[T])

case class CircularList[T](first: Node[T], last: Node[T], current: Node[T]) 
  extends Circular[T] {

  // Nodes in the list from the current position up to but not including the end
  def toStream(end: CircularList[T]): Stream[CircularList[T]] = {
    @tailrec
    def toStreamRec(end: CircularList[T], acc: Stream[CircularList[T]]): Stream[CircularList[T]] = {
      if (this == end) {
        acc
      } else {
        toStreamRec(end.prev, Stream.cons(end.prev, acc))
      }
    }
    toStreamRec(end, Stream.empty)
  }

  def prev: CircularList[T] = new CircularList[T](first, last, current.prev)
  ...

so toStream maps to get in my cutdown example.

Upvotes: 1

Views: 172

Answers (1)

SergGr
SergGr

Reputation: 23788

What you want is something called F-bound generic. The code goes like this:

trait Base[T <: Base[T]] {
  def next: T
  def get(end: T): T
}

class Chlid extends Base[Child] {
  def next: Chlid = new Chlid()
  def get(end: Chlid): Chlid = end.next
}

Your code doesn't compile because

def get(end: B): B

is not an override of

def get(end: A): A

because the original method accepts objects of type A while your method requires only more narrow type B


For your Circular example you want something like

trait Circular[T, C <: Circular[T, C]] {
  // Nodes in the list from the current position up to but NOT INCLUDING the end
  def toStream(end: C): Stream[C]

  def next: C
}

case class Node[T](val data: T, var prev: Node[T], var next: Node[T])

case class CircularList[T](first: Node[T], last: Node[T], current: Node[T]) extends Circular[T, CircularList[T]] {

  // Nodes in the list from the current position up to but not including the end
  def toStream(end: CircularList[T]): Stream[CircularList[T]] = {
    @tailrec
    def toStreamRec(end: CircularList[T], acc: Stream[CircularList[T]]): Stream[CircularList[T]] = {
      if (this == end) {
        acc
      } else {
        toStreamRec(end.prev, Stream.cons(end.prev, acc))
      }
    }

    toStreamRec(end, Stream.empty)
  }

  def prev: CircularList[T] = new CircularList[T](first, last, current.prev)

  override def next: CircularList[T] = ???
}

Upvotes: 3

Related Questions