diff --git a/core/src/main/scala-2.12/cats/compat/ChainCompat.scala b/core/src/main/scala-2.12/cats/compat/ChainCompat.scala new file mode 100644 index 0000000000..3d6efc06bb --- /dev/null +++ b/core/src/main/scala-2.12/cats/compat/ChainCompat.scala @@ -0,0 +1,15 @@ +package cats.data + +private[data] trait ChainCompat[+A] { _: Chain[A] => + + /** + * The number of elements in this chain, if it can be cheaply computed, -1 otherwise. + * Cheaply usually means: Not requiring a collection traversal. + */ + final def knownSize: Long = + this match { + case Chain.Empty => 0 + case Chain.Singleton(_) => 1 + case _ => -1 + } +} diff --git a/core/src/main/scala-2.13+/cats/data/ChainCompat.scala b/core/src/main/scala-2.13+/cats/data/ChainCompat.scala new file mode 100644 index 0000000000..a02439c188 --- /dev/null +++ b/core/src/main/scala-2.13+/cats/data/ChainCompat.scala @@ -0,0 +1,17 @@ +package cats +package data + +private[data] trait ChainCompat[+A] { self: Chain[A] => + + /** + * The number of elements in this chain, if it can be cheaply computed, -1 otherwise. + * Cheaply usually means: Not requiring a collection traversal. + */ + final def knownSize: Long = + this match { + case Chain.Empty => 0 + case Chain.Singleton(_) => 1 + case Chain.Wrap(seq) => seq.knownSize.toLong + case _ => -1 + } +} diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 1b8d4f7229..b302258509 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -32,7 +32,7 @@ import Chain.{ * O(1) `uncons`, such that walking the sequence via N successive `uncons` * steps takes O(N). */ -sealed abstract class Chain[+A] { +sealed abstract class Chain[+A] extends ChainCompat[A] { /** * Returns the head and tail of this Chain if non empty, none otherwise. Amortized O(1). @@ -566,12 +566,19 @@ sealed abstract class Chain[+A] { * Returns the number of elements in this structure */ final def length: Long = { - // TODO: consider optimizing for `Chain.Wrap` case. - // Some underlying seq may not need enumerating all elements to calculate its size. - val iter = iterator - var i: Long = 0 - while (iter.hasNext) { i += 1; iter.next(); } - i + @annotation.tailrec + def loop(chains: List[Chain[A]], acc: Long): Long = + chains match { + case Nil => acc + case h :: tail => + h match { + case Empty => loop(tail, acc) + case Wrap(seq) => loop(tail, acc + seq.length) + case Singleton(a) => loop(tail, acc + 1) + case Append(l, r) => loop(l :: r :: tail, acc) + } + } + loop(this :: Nil, 0L) } /** @@ -579,19 +586,6 @@ sealed abstract class Chain[+A] { */ final def size: Long = length - /** - * The number of elements in this chain, if it can be cheaply computed, -1 otherwise. - * Cheaply usually means: Not requiring a collection traversal. - */ - final def knownSize: Long = - // TODO: consider optimizing for `Chain.Wrap` case – call the underlying `knownSize` method. - // Note that `knownSize` was introduced since Scala 2.13 only. - this match { - case _ if isEmpty => 0 - case Chain.Singleton(_) => 1 - case _ => -1 - } - /** * Compares the length of this chain to a test value. *