Search code examples
scalascala-collectionsimplicitgeneric-programmingimplicit-typing

how to reparameterize collection type


I want to define a transformation of a generic collection.

def transform[S<:GenIterable[T],T](s:S):S = s.zipWithIndex

This throws:

type mismatch; found : scala.collection.GenIterable[(T, Int)] required: S

I have to declare S as a parameterized GenIterable in the function definition. I'd like to specify an output type of "whatever the collection type was that created S, except parameterized with [(T,Int)]", so that I'm guaranteed to get the same collection type back, and not just GenIterable.

How can I make that happen?


Solution

  • That's my stub on it:

    def transform[S[T]<: GenIterableLike[T,S[T]],T](s:S[T])
      (implicit bf:CanBuildFrom[S[T],(T,Int),S[(T,Int)]]):S[(T,Int)] = {
    
      s.zipWithIndex
    }
    

    Signature is horrible, it may be somewhat simplified (or obscured) by replacing T with _.

    Trying it out:

    scala> val m = Map("x"->"1","y"->"2")
    m: scala.collection.Map[String,String] = Map(x -> 1, y -> 2)
    
    scala> transform(m)
    res3: Iterable[((String, String), Int)] = List(((x,1),0), ((y,2),1))
    
    scala> val s = Set("x", "y")
    s: scala.collection.Set[String] = Set(x, y)
    
    scala> transform(s)
    res4: scala.collection.Set[(String, Int)] = Set((x,0), (y,1))
    
    scala> val seq = List("x", "y")
    seq: List[String] = List(x, y)
    
    scala> transform(seq)
    res5: List[(String, Int)] = List((x,0), (y,1))
    

    The key is to use Like trait because it carries the type of the collection being used. Then, implicit conversions take care of the rest. Note, how Map is handled. It seems that implicit conversions default to converting Iterable to List, which makes sense.