Last active
January 13, 2016 20:04
-
-
Save oxbowlakes/84e120026ded7f7834b8 to your computer and use it in GitHub Desktop.
Demonstration of a broken TraversableOnce implementation of reduceLeft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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