Skip to content

Instantly share code, notes, and snippets.

@oxbowlakes
Last active January 13, 2016 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oxbowlakes/84e120026ded7f7834b8 to your computer and use it in GitHub Desktop.
Save oxbowlakes/84e120026ded7f7834b8 to your computer and use it in GitHub Desktop.
Demonstration of a broken TraversableOnce implementation of reduceLeft
//I have a Java interface which looks like this
trait ConsumableResource[A <: AnyRef] extends AutoCloseable {
/** returns `null` when we are ended */
def take(): A
}
//I would like to make this viewable as a scala `TraversableOnce` where the resource is automatically closed at the end of the traversal
//I must go *via* a `Traversable` as I cannot override `isTraversableAgain` on a `Traversable` because it's declared as final
private final class ConsumableResourceTraversable[A <: AnyRef](self: ConsumableResource[A]) extends Traversable[A] {
var a: A = _
override def foreach[U](f: A => U) = try {
do {
a = self.take()
if (a ne null) f(a)
} while (a ne null)
} finally self.close()
}
//finally I return a `TraversableOnce` via an *explicit implicit*
implicit class ConsumableResourceOps[A](val self: ConsumableResource[A]) extends AnyVal {
def asTraversableOnce: TraversableOnce[A] = {
val impl = new ConsumableResourceTraversable[A](self)
new TraversableOnce[A] {
override def foreach[U](f: (A) => U) = impl foreach f
override def seq = this
override final def hasDefiniteSize = false
override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int) = impl.copyToArray(xs, start, len)
override def forall(p: (A) => Boolean) = impl forall p
override def toTraversable = impl.toTraversable
override def isEmpty = impl.isEmpty
override def find(p: (A) => Boolean) = impl find p
override def exists(p: (A) => Boolean) = impl exists p
override def toStream = impl.toStream
override def toIterator = impl.toIterator
override final def isTraversableAgain = false //please note I can override isTraversableAgain here
}
}
}
//The problem arises if I have a resource and reduce over it
val r: ConsumableResource[String] = ???
r.asTraversableOnce.reduceLeft(_ ++ _)
//the reduceLeft implementation comes from TraversableOnce
trait TraversableOnce[+A] extends Any with GenTraversableOnce[A] {
self =>
def reduceLeft[B >: A](op: (B, A) => B): B = {
if (isEmpty) //Oh noes!
throw new UnsupportedOperationException("empty.reduceLeft")
var first = true
var acc: B = 0.asInstanceOf[B]
for (x <- self) {
if (first) {
acc = x
first = false
}
else acc = op(acc, x)
}
acc
}
//...
}
//But, oh no! It calls `isEmpty`. This method is (laughably) documented on `TraversableOnce`
/** Self-documenting abstract methods. */
def isEmpty: Boolean
//(Is it indeed?)
//But the `TraversableOnce` implementation of reduceLeft have assumed that they can call `isEmpty` without "consuming" the traversal
//I believe that this is not a reasonable assumption to make and certainly not one which is undocumented
//My `isEmpty` implementation comes from `TraversableLike` (of course) and hence looks like this (it consumes the resource):
def isEmpty: Boolean = {
var result = true
breakable {
for (x <- this) {
result = false
break
}
}
result
}
//Hence the `reduceLeft` operations on `TraversableOnce` make an unwarranted assumption about the `isEmpty` method which is
//not supported by how it has been documented
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment