Search code examples
scalacovariancepath-dependent-type

Scala copy class with path dependent types and covariant parameter


I have some trouble with path dependent types and covariant parameters. I need to create a new Instance of SomeFancyCollection but it won't compile because of the Path dependent types. When I pull all the stuff like Node and Branch out of the class i have to declare the root paramter as private[this] and Iam also not able to get a new Instance of the class. My code looks like:

class SomeFancyCollection[+A] {

  private var root: Node = Branch(0x0, Vector[Node]())

  trait Node {
    val id: Byte
    def put[A1 >: A](ids: Seq[Byte], item: A1): Node
    def remove[A1 >: A](ids: Seq[Byte], item: A1): Node
  }

  case class Branch(id: Byte, subs: Vector[Node]) extends Node {
      ...
  }

  case class Leaf[A1 >: A](id: Byte, subs: Vector[A1]) extends Node {
      ...
  }

  def add[A1 >: A](item: A1): SomeFancyCollection[A1] = {
    val ids: Seq[Byte] = getIds() // doesn't matter
    val newRoot = root.put(ids, item)
    val newInstance = new SomeFancyCollection[A1]()
    newInstance.root = newRoot
    newInstance
  }
}

Solution

  • In your code, there is no obvious reason to make Node and Leaf nested path-dependent classes. Just make them covariant stand-alone classes, then all the problems with path-dependence go away:

    trait Node[+A] {
      val id: Byte
      def put[A1 >: A](ids: Seq[Byte], item: A1): Node[A1]
      def remove[A1 >: A](ids: Seq[Byte], item: A1): Node[A1]
    }
    
    case class Branch[+A](id: Byte, subs: Vector[Node[A]]) extends Node[A] {
    
      def put[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
      def remove[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
    }
    
    case class Leaf[+A](id: Byte, subs: Vector[A]) extends Node[A] {
    
      def put[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
      def remove[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
    }
    
    
    
    class SomeFancyCollection[+A](val root: Node[A] = Branch(0x0, Vector[Node[A]]())) {
      def add[A1 >: A](item: A1): SomeFancyCollection[A1] = {
        val ids: Seq[Byte] = ???// getIds() // doesn't matter
        val newRoot = root.put(ids, item)
        new SomeFancyCollection[A1](newRoot)
      }
    }
    

    If you don't want to pollute the namespace, just declare the Node classes package-private, or even hide all those auxiliary implementation-detail classes inside the companion object of SomeFancyCollection:

    class SomeFancyCollection[+A] private[SomeFancyCollection](
      val root: SomeFancyCollection.AnnoyingDetails.Node[A]
    ) {
      def add[A1 >: A](item: A1): SomeFancyCollection[A1] = {
        val ids: Seq[Byte] = ???// getIds() // doesn't matter
        val newRoot = root.put(ids, item)
        new SomeFancyCollection[A1](newRoot)
      }
    }
    
    object SomeFancyCollection {
    
      def empty[A]: SomeFancyCollection[A] = new SomeFancyCollection[A](
        AnnoyingDetails.Branch(0x0, Vector[AnnoyingDetails.Node[A]]())
      )
    
      private[SomeFancyCollection] object AnnoyingDetails {
        trait Node[+A] {
          val id: Byte
          def put[A1 >: A](ids: Seq[Byte], item: A1): Node[A1]
          def remove[A1 >: A](ids: Seq[Byte], item: A1): Node[A1]
        }
    
        case class Branch[+A](id: Byte, subs: Vector[Node[A]]) extends Node[A] {
          def put[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
          def remove[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
        }
    
        case class Leaf[+A](id: Byte, subs: Vector[A]) extends Node[A] {
          def put[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
          def remove[A1 >: A](ids: Seq[Byte], item: A1): Node[A1] = ???
        }
      }
    }