Search code examples
scaladictionarycollectionsfor-comprehension

Scala custom collection fails to pick correct map function in for-comprehension


I've implemented a custom collection class which is basically a Map with implicit integer keys and values that are subclasses of AnyRef. It uses the Int keys as index for underlying array structure. Here is the class declaration signature (class instantiation is done in companion object, hence private constructor):

class ArrayMap[T >: Null <: AnyRef: ClassTag] private (private var data: Array[T]) { self =>
   ...
}

Now I want to add required methods for for-comprehension. I've defined two different map functions. One that returns a List and the other one returns the same data type (ArrayMap).

def map[X](f: (Int, T) => X): List[X] = { ... }
def map[X >: Null <: AnyRef: ClassTag](f: (Int, T) => X): ArrayMap[X] = { ... }
def foreach(f: (Int, T) => Unit): Unit = { ... }
def flatMap[X >: Null <: AnyRef: ClassTag](f: (Int, T) => Iterable[(Int, X)]): ArrayMap[X] = { ... }
def filter(p: (Int, T) => Boolean): ArrayMap[T] = { ... }

No implicit is defined. Above functions work as expected when used separately. The problem is in for-comprehensions. For loop either picks the first map which returns List or throws a mysterious error. The following example produces error:

val map = ArrayMap.empty[Integer]
map(0) = 0
map(1) = 1
map(5) = 2
map(6) = 3
map(10) = 4

val rs: ArrayMap[String] = for (e <- map) yield e._2.toString

Above code throws:

Error:(293, 41) missing parameter type
        val rs: ArrayMap[String] = for (e <- map) yield e._2.toString

What am I missing?

[UPDATE]

The full implementation is available as a gist here.


Solution

  • The problem is related to a type mismatch, you defined the function to pass to map as a function of two arguments (Int & T) to X. while in your for comprehension you treat it as a function of one argument (a tuple (Int, T)) to X.

    The simplest solution is to redefine your map function signature. e.g.

    import scala.reflect.ClassTag
    
    class ArrayMap[T >: Null <: AnyRef: ClassTag] (val data: Array[T]) {
      // Note the double parenthesis (()).
      def map[X >: Null <: AnyRef: ClassTag](f: ((Int, T)) => X): ArrayMap[X] = ???
      def withFilter(p: ((Int, T)) => Boolean): ArrayMap[T] = ???
    }
    

    With that definition you can make something like

    val map: ArrayMap[java.lang.Integer] = new ArrayMap(Array(1, 2, 3))
    
    // Note I use lazy val to avoid the NotImplementedException.
    lazy val rs1: ArrayMap[String] = map.map(tuple => tuple._2.toString)
    
    lazy val rs2: ArrayMap[String] = map.map { case (_, v) => v.toString }
    
    lazy val rs3: ArrayMap[String] = for {
      tuple <- map
    } yield tuple._2.toString
    
    lazy val rs4: ArrayMap[String] = for {
      (_, v) <- map
    } yield v.toString
    

    See the full signature of map in Scala Map as a reference.