Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a generic algorithm for any Traversable in Scala?

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.

like image 732
user2024523 Avatar asked Jan 30 '13 09:01

user2024523


People also ask

What is traversable in Scala?

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.

What is generic function in Scala?

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.


1 Answers

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))
like image 182
Régis Jean-Gilles Avatar answered Sep 20 '22 10:09

Régis Jean-Gilles