Question

How do I add a foreachWithIndex method on Scala collections?

This is what I could come up with so far:

implicit def iforeach[A, CC <: TraversableLike[A, CC]](coll: CC) = new {
  def foreachWithIndex[B](f: (A, Int) => B): Unit = {
    var i = 0
    for (c <- coll) {
      f(c, i)
      i += 1
    }
  }
}

This doesn't work:

Vector(9, 11, 34).foreachWithIndex { (el, i) =>
  println(el, i)
}

Raises the following error:

error: value foreachWithIndex is not a member of scala.collection.immutable.Vector[Int]
Vector(9, 11, 34).foreachWithIndex { (el, i) =>

However the code works when I explicitly apply the conversion method:

iforeach[Int, Vector[Int]](Vector(9, 11, 34)).foreachWithIndex { (el, i) =>
  println(el, i)
}

Output:

(9,0)
(11,1)
(34,2)

How do I make it make it work without explicit application of a conversion method? Thanks.

Was it helpful?

Solution

You need to extend Iterable:

class RichIter[A, C](coll: C)(implicit i2ri: C => Iterable[A]) {
    def foreachWithIndex[B](f: (A, Int) => B): Unit = {
    var i = 0
    for (c <- coll) {
      f(c, i)
      i += 1
    }
  }
}

implicit def iter2RichIter[A, C[A]](ca: C[A])(
    implicit i2ri: C[A] => Iterable[A]
): RichIter[A, C[A]] = new RichIter[A, C[A]](ca)(i2ri)

Vector(9, 11, 34) foreachWithIndex {
  (el, i) => println(el, i)
}

output:

(9,0)
(11,1)
(34,2)

See this post by Rex Kerr for more information.

OTHER TIPS

The short answer is that you have to parameterize CC if you do it that way or the type inferencer can't figure out what A is. The other short answer is do it the way I describe in the answer to this question.

To expand a little bit more, there's really no reason that you need CC <: TraversableLike--just have it take a Traversable and start with iforeach[A](coll: Traversable[A])! You don't need to use fancy type bounds in order to use a superclass/supertrait. If you want to do something more complicated where you return another collection with collection type preserved, then you need to use builders and such, which I describe in the other question.

If what you're interested in is only iterating with an index, you might as well just skip the whole pimping part and do something like

coll.zipWithIndex.foreach { case (elem, index) =>
  /* ... */
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top