More iterators goodness

Following-up on the previous post about iterators, say we have a clean Scala API with Option instead:

class Foo { def parent: Option[Foo] =  … }
def findBar(foo: Foo) = Option[Bar] = …

It turns out that Iterator.iterate falls apart with Option, because it requires a function returning a plain T. But we can write a better Option-aware iterate function:

def iterate[T](start: T)(f: T ⇒ Option[T]): Iterator[T] = new Iterator[T] {
  private[this] var acc = Option(start)
  def hasNext = acc.isDefined
  def next() = {
    val result = acc.get
    acc = f(result)
    result
  }
}

Note that this iterate does not depend at all on application-specific code. In fact, it could be part of the standard Scala library!

With this new tool, the new ancestorOrSelf iterator simply looks like this:

def ancestorOrSelf(foo: Foo) =
  iterate(foo)(_.parent)

And one way to write findInAncestorOrSelf becomes:

def findInAncestorOrSelf(foo: Foo) =
  ancestorOrSelf(foo) map findBar find (_.isDefined) flatten

Now to be honest this last function is not fully satisfying, with its use of isDefined and its hanging flatten in the end! There are other ways to write it, including with collectFirst, but still it’s not quite right.

The root issue is that Scala.Iterator doesn’t have a headOption (or a nextOption) method. Until that’s the case we can write our own extension method like this:

class MyIteratorOps[T](i: Iterator[T]) { def nextOption = if (i.hasNext) Option(i.next) else None }
implicit def toIteratorOps[T](i: Iterator[T]) = new MyIteratorOps(i)

or, with Scala 2.10:

implicit class MyIteratorOps(i: Iterator[T]) {
  def nextOption = if (i.hasNext) Option(i.next) else None
}

Again, this is something which could be in the standard Scala library. With this the final code becomes the much cleaner:

def findInAncestorOrSelf(foo: Foo) =
  ancestorOrSelf(foo) flatMap findBar nextOption

In the end it’s probably a matter of taste, but I would almost bet that as a programmer familiar with Scala collections, you would find this function immediately comprehensible. That’s a huge win in addition to the increased modularity gained through the use of iterators.