How to implement newBuilder for a custom Scala col

2020-07-23 04:08发布

问题:

I'm attempting to implement a new collection type which follows the same idioms as the standard library, but am having trouble figuring out how to handle the Builder mechanics. I've read through the excellent "Architecture of Scala Collections" doc page, but it doesn't cover my situation.

Here's a simplified version of what I'm trying to do:

import scala.collection.TraversableLike
import scala.concurrent.Future

trait AsyncMap[A, +B]
  extends Traversable[(A, B)]
  with TraversableLike[(A, B), AsyncMap[A, B]]
{

  def empty: AsyncMap[A, B]

  // This is the main difference from scala.collection.Map (an AsyncMap doesn't
  // block while it checks if it contains an element for a given key).
  def get(key: A): Future[Option[B]]

  def +[B1 >: B](kv: (A, B1)): AsyncMap[A, B1]

}

Compiling the above code gives me an error:

error: overriding method newBuilder in trait TraversableLike of type => scala.collection.mutable.Builder[(A, B),AsyncMap[A,B]];
 method newBuilder in trait GenericTraversableTemplate of type => scala.collection.mutable.Builder[(A, B),Traversable[(A, B)]] has incompatible type
trait AsyncMap[A, +B]
      ^

I think what this is complaining about is that GenericTraversableTemplate has a concrete newBuilder implementation whose signature is incompatible with the one that TraversableLike is looking for. What I don't understand is how I can get around this.

Implementing newBuilder: Builder[(A, B), Traversable[(A, B)]] produces this error:

error: overriding method newBuilder in trait TraversableLike of type => scala.collection.mutable.Builder[(A, B),AsyncMap[A,B]];
 method newBuilder has incompatible type
  override def newBuilder: Builder[(A, B), Traversable[(A, B)]] = {
               ^

While implementing newBuilder: Builder[(A, B), AsyncMap[A, B]] produces this error:

error: covariant type B occurs in contravariant position in type => scala.collection.mutable.Builder[(A, B),AsyncMap[A,B]] of method newBuilder
  override def newBuilder: Builder[(A, B), AsyncMap[A, B]] = {
               ^

I think I'm on the right track with the latter approach, but am not sure how to specify the variance here.

I've also tried making this look more like the internal collections by implementing a trait AsyncMapLike[A, +B, +This <: AsyncMapLike[A, B, This] with AsyncMap[A, B]], but that approach hasn't borne any fruit.

I should admit that I'm pretty new to Scala and while I think I understand its type system I could be unaware of some type operator or simple design pattern that solves this.

Any help would be greatly appreciated.


Possibly-related questions:

  • Creating typed collection
  • Specific Builder for Parameterized Type
  • Scala Inheritance; Builder Trouble; Non-Generic IterableLike
  • How do I specify a newBuilder for a scala set?
  • Error: Covariant type A occurs in contravariant position
  • covariant type T occurs in contravariant position

回答1:

As mucaho helped me discover in the comments above, it turns out that my problem was caused by a missing access modifier. The error message about variance still doesn't make sense to me (I've opened a new question about it: Why does scalac only emit variance errors with certain access modifiers?), but when I override newBuilder with a concrete implementation whose access is protected[this], everything works as expected (previously I had been trying to make it public).

import scala.collection.mutable.Builder
import scala.collection.TraversableLike
import scala.concurrent.Future

trait AsyncMap[A, +B]
  extends Traversable[(A, B)]
  with TraversableLike[(A, B), AsyncMap[A, B]]
{
  def empty: AsyncMap[A, B]

  def get(key: A): Future[Option[B]]

  def +[B1 >: B](kv: (A, B1)): AsyncMap[A, B1]

  // This works!
  override protected[this] def newBuilder: Builder[(A, B), AsyncMap[A, B]] = ???
}