4

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.

0__
  • 66,707
  • 21
  • 171
  • 266
missingfaktor
  • 90,905
  • 62
  • 285
  • 365

3 Answers3

8

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.

Community
  • 1
  • 1
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
4

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.

Community
  • 1
  • 1
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Actually it's just one method from the set of extra methods I am trying to implement. Some of those other methods need constructing a new collection of the same type. – missingfaktor Jul 25 '11 at 23:04
2

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) =>
  /* ... */
}
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234