Search code examples
scala

how to override scala SeqFactory while using ClassTag


I am learning from this code snippest https://gist.github.com/tiqwab/bc8d372ca489a74b72dd2357e7d6b010 to create a custom scala Seq class.

package com.tiqwab.example.step4

import scala.collection.mutable.ListBuffer
import scala.collection.{immutable, mutable, SeqFactory, StrictOptimizedLinearSeqOps}

class MyList[+A: ClassTag] private (elems: List[A])
  extends immutable.LinearSeq[A] with immutable.LinearSeqOps[A, MyList, MyList[A]]
  with StrictOptimizedLinearSeqOps[A, MyList, MyList[A]] {
  // To be overridden in implementations, as said in scala.collection.LinearSeq
  override def isEmpty: Boolean = elems.isEmpty
  override def head: A = elems.head
  override def tail: MyList[A] = new MyList(elems.tail)

  override def iterator: Iterator[A] = elems.iterator

  // To be overridden for LinearSeqOps
  override def iterableFactory: SeqFactory[MyList] = MyList

  override def toString: String = s"MyList(${elems.mkString(",")})"
}

object MyList extends SeqFactory[MyList] {
  override def from[A](source: IterableOnce[A]): MyList[A] =
    newBuilder[A].addAll(source).result()
  override def empty[A]: MyList[A] =
    new MyList(List.empty)
  override def newBuilder[A]: mutable.Builder[A, MyList[A]] =
    new ListBuffer[A].mapResult(elems => new MyList(elems))
}

The question is how can I attach a ClassTag to the type parameter A, if I literally attach the tag to that, I can't override the methods from SeqFactory anymore. Because the method signature is no longer consistent with the one from SeqFactory. Or it there a better way to implement such Custom collection with class tag?

package com.tiqwab.example.step4

import scala.collection.mutable.ListBuffer
import scala.collection.{immutable, mutable, SeqFactory, StrictOptimizedLinearSeqOps}

class MyList[+A] private (elems: List[A])
  extends immutable.LinearSeq[A] with immutable.LinearSeqOps[A, MyList, MyList[A]]
  with StrictOptimizedLinearSeqOps[A, MyList, MyList[A]] {
  // To be overridden in implementations, as said in scala.collection.LinearSeq
  override def isEmpty: Boolean = elems.isEmpty
  override def head: A = elems.head
  override def tail: MyList[A] = new MyList(elems.tail)

  override def iterator: Iterator[A] = elems.iterator

  // To be overridden for LinearSeqOps
  override def iterableFactory: SeqFactory[MyList] = MyList

  override def toString: String = s"MyList(${elems.mkString(",")})"
}

object MyList extends SeqFactory[MyList] {
  override def from[A: ClassTag](source: IterableOnce[A]): MyList[A] =
    newBuilder[A].addAll(source).result()
  override def empty[A: ClassTag]: MyList[A] =
    new MyList(List.empty)
  override def newBuilder[A: ClassTag]: mutable.Builder[A, MyList[A]] =
    new ListBuffer[A].mapResult(elems => new MyList(elems))
}

Solution

  • You'd have to modify the factory, so that it would NOT be an object but a class with a constructor, and pass ClassTag there:

    class MyListFactory[A: ClassTag] {
      override def from[A](source: IterableOnce[A]): MyList[A] =
        newBuilder[A].addAll(source).result()
      override def empty[A]: MyList[A] =
        new MyList(List.empty)
      override def newBuilder[A]: mutable.Builder[A, MyList[A]] =
        new ListBuffer[A].mapResult(elems => new MyList(elems))
    }
    
    object MyList {
    
      // one way of creating this things
      def apply[A: ClassTag]: MyListFactory[A] = new MyListFactory[A]
    
      // another way, requires enabling implicit conversions
      implicit def convertToFactory[A: ClassTag](
        notUsed: MyList.type // maybe add some @unused annotation here
      ): MyListFactory[A] = new MyListFactory[A]
    }
    

    Or, you can also take a look how stdlib implemented e.g. SortedSet since it requires Ordering[A], although your code might benefit from using ClassTagIterableFactory instead of SortedIterableFactory.

    object MyList extends ClassTagIterableFactory[MyList] {
      override def from[A: ClassTag](source: IterableOnce[A]): MyList[A] =
        newBuilder[A].addAll(source).result()
      override def empty[A: ClassTag]: MyList[A] =
        new MyList(List.empty)
      override def newBuilder[A: ClassTag]: mutable.Builder[A, MyList[A]] =
        new ListBuffer[A].mapResult(elems => new MyList(elems))
    }
    

    It should be more aligned with what you're trying to achieve (although I would avoid defining a custom collection if I didn't have a good reason to do so).