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.
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.