Skip to content

Instantly share code, notes, and snippets.

@laughedelic
Created September 14, 2017 03:26
Show Gist options
  • Save laughedelic/815999b02ea0656bd55a8d0cc963a0dd to your computer and use it in GitHub Desktop.
Save laughedelic/815999b02ea0656bd55a8d0cc963a0dd to your computer and use it in GitHub Desktop.
Attach a `finally` block to an iterator
implicit class IteratorOps[X](val iterator: Iterator[X]) extends AnyVal {
/** Attaches a block that will be executed only when the iterator is _completely_ consumed
* @note The original iterator should be discarded after calling this method
*/
def withFinally(fin: => Unit): Iterator[X] = new Iterator[X] {
def next(): X = iterator.next()
def hasNext: Boolean = {
val has = iterator.hasNext
if (!has) fin
has
}
}
}
@laughedelic
Copy link
Author

This is very handy when you want to transform (map) an iterator with some side-effects on the way and get the used resources closed in the end. For example, you can define such method:

implicit class FileWritingOps(val file: File) extends AnyVal {

  /** Reads a GZipped file and returnes its lines iterator */
  def gzippedLines: Iterator[String] = {
    val gz = new GZIPInputStream(Files.newInputStream(file.toPath))
    val reader = new BufferedReader(new InputStreamReader(gz))
    reader.lines().iterator().asScala.andFinally {
      reader.close()
      gz.close()
    }
  }
}

Note, that you can't just close stream, because it will happen before any iterator element is evaluated.

val lines: Iterator[String] = new File("lines.txt.gz").gzippedLines

Here nothing happened yet, you just defined a new iterator.

And only after you consume it completely: lines.foreach(println), then gz and reader will get closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment