I'm implementing a generic algorithm to return a collection based on two other collections. The problem can be simplified to
def add[Repr <: Traversable[_]](coll1: Repr, coll2: Repr) = coll1 ++ coll2
The problem occurred when I applied the algorithm on a collection A I've defined as
class A[T] extends Iterable[(Int,T)] with IterableLike[(Int,T), A[T]] { ... }
i.e., the type parameter of A is not the same as for the inherited Iterable. Map uses a similar approach.
Example with Map:
scala> val m1 = Map("a" -> 1, "b" -> 1, "c" -> 1)
m1: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 1, c -> 1)
scala> val m2 = Map("a" -> 2, "c" -> 1)
m2: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 2, c -> 1)
Applying add with m1 and m2 as parameters results in a List:
scala> add(m1,m2)
res3: Traversable[Any] = List((a,1), (b,1), (c,1), (a,2), (c,1))
...while the wanted result would be similar to using the ++ method directly:
scala> m1 ++ m2
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 2, b -> 1, c -> 1)
This problem does not occur using a collection B defined as:
class B[T] extends Iterable[T] with IterableLike[T, B[T]] { ... }
e.g., Queue is implemented in a similar way.
Example with Queue:
scala> val q1 = Queue(9,2,5)
q1: scala.collection.immutable.Queue[Int] = Queue(9, 2, 5)
scala> val q2 = Queue(7,3,1)
q2: scala.collection.immutable.Queue[Int] = Queue(7, 3, 1)
Applying add on q1 and q2 gives the wanted result:
scala> add(q1,q2)
res4: Traversable[Any] = Queue(9, 2, 5, 7, 3, 1)
Question: Is there a way to implement add, such that the result will be the same as when using the ++ method directly, for all kinds of travesables (including collections implemented similar to Map)? I have been trying to implement an implicit CanBuildFrom in the companion object of class A, with no luck. It seems to me that the problem is with the algorithm, not the collection implementations since it does not work for Map either.
Traversable is accomplished by the Scala's Collection classes. It is obligatory for Traversable to define foreach method only, as Traversable can inherit all the other methods. The foreach method could traverse all the elements of the Scala's collection class.
One of the best ways to understand use cases for generic classes is to look at examples in the Scala standard library. Most Scala generic classes are collections, such as the immutable List, Queue, Set, Map, or their mutable equivalents, and Stack. Collections are containers of zero or more objects.
Given that add
is nothing more than an alias for TraversableLike.++
, the first step is too look at ++
's signature:
trait TraversableLike[+A, +Repr] extends ... {
...
def ++:[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That
...
}
Then all you have to do is to turn this
into the first parameter, and that
into the second parameter:
import collection.TraversableLike
import collection.generic.CanBuildFrom
def add[A, Repr, B >: A, That](coll1: TraversableLike[A, Repr], coll2: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
coll1 ++ coll2
}
UPDATE: I also investigated into what you would need to do to have A
behave properly with respect to add
. As it stands (without doing anything special ), add applied on instances of A
returns an Iterable
instead of an A
:
import collection.{IterableLike, TraversableLike}
// Dummy `A` implementation, for illustration
class A[T]( val inner: Seq[(Int, T)] ) extends Iterable[(Int,T)] with IterableLike[(Int,T), A[T]] {
def iterator: Iterator[(Int, T)] = inner.iterator
override protected[this] def newBuilder: scala.collection.mutable.Builder[(Int, T),A[T]] = ???
def :+(elem: (Int, T) ): A[T] = new A[T]( inner :+ elem )
}
object A {
def apply[T]( elems: (Int, T)* ) = new A( elems )
}
val a1 = A( 1-> "one", 2 -> "two" )
val a2 = A( 3-> "three", 4 -> "four", 5 -> "five" )
add(a1, a2)
The result is:
res0: Iterable[(Int, String)] = List((1,one), (2,two), (3,three), (4,four), (5,five))
Here is what I cam up by tibkering with CanBuildFrom
. I cannot warrant that this is the best example but it does work (by which I mean we get an A
as result when calling add
):
import collection.IterableLike
import collection.generic.CanBuildFrom
import collection.mutable.Builder
class A[T]( val inner: Seq[(Int, T)] ) extends Iterable[(Int,T)] with IterableLike[(Int,T), A[T]] {
def iterator: Iterator[(Int, T)] = inner.iterator
override protected[this] def newBuilder: scala.collection.mutable.Builder[(Int, T),A[T]] = new A.ABuilder[T]
def :+(elem: (Int, T) ): A[T] = new A[T]( inner :+ elem )
}
object A {
private val _empty = new A[Nothing]( Nil )
def empty[T]: A[T] = _empty.asInstanceOf[A[T]]
def apply[T]( elems: (Int, T)* ) = new A( elems )
class ABuilder[T] extends Builder[(Int,T), A[T]] {
protected var elems: A[T] = empty
def +=(x: (Int, T)): this.type = { elems = elems :+ x; this }
def clear() { elems = empty }
def result: A[T] = elems
}
implicit def canBuildFrom[T]: CanBuildFrom[A[_], (Int,T), A[T]] = new CanBuildFrom[A[_], (Int,T), A[T]] {
def apply(from: A[_]) = apply()
def apply() = new ABuilder[T]
}
}
Now the result is:
res0: A[String] = ((1,one), (2,two), (3,three), (4,four), (5,five))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With