diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 54303abdd39..e89c2a468e0 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -1,6 +1,7 @@ package cats import scala.collection.mutable +import cats.std.long._ import simulacrum.typeclass /** @@ -56,6 +57,16 @@ import simulacrum.typeclass } } + /** + * The size of this Foldable. + * + * This is overriden in structures that have more efficient size implementations + * (e.g. Vector, Set, Map). + * + * Note: will not terminate for infinite-sized collections. + */ + def size[A](fa: F[A]): Long = foldMap(fa)(_ => 1) + /** * Fold implemented using the given Monoid[A] instance. */ diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 1541ceeaff4..7dc75ac9cf3 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -116,6 +116,8 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { implicit def catsDataReducibleForOneAnd[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F,?], F] { override def split[A](fa: OneAnd[F,A]): (A, F[A]) = (fa.head, fa.tail) + + override def size[A](fa: OneAnd[F, A]): Long = 1 + F.size(fa.tail) } implicit def catsDataMonadForOneAnd[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[F, ?]] = diff --git a/core/src/main/scala/cats/std/map.scala b/core/src/main/scala/cats/std/map.scala index 9ee18cb6894..9e9a1fd9659 100644 --- a/core/src/main/scala/cats/std/map.scala +++ b/core/src/main/scala/cats/std/map.scala @@ -45,6 +45,8 @@ trait MapInstances extends cats.kernel.std.MapInstances { def foldRight[A, B](fa: Map[K, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = Foldable.iterateRight(fa.values.iterator, lb)(f) + override def size[A](fa: Map[K, A]): Long = fa.size.toLong + override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty } } diff --git a/core/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala index 09da1a88de0..0c95763dec2 100644 --- a/core/src/main/scala/cats/std/set.scala +++ b/core/src/main/scala/cats/std/set.scala @@ -18,6 +18,8 @@ trait SetInstances extends cats.kernel.std.SetInstances { def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = Foldable.iterateRight(fa.iterator, lb)(f) + override def size[A](fa: Set[A]): Long = fa.size.toLong + override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = fa.exists(p) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 62ead466a35..07fb55430f8 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -44,6 +44,8 @@ trait VectorInstances extends cats.kernel.std.VectorInstances { Eval.defer(loop(0)) } + override def size[A](fa: Vector[A]): Long = fa.size.toLong + def traverse[G[_], A, B](fa: Vector[A])(f: A => G[B])(implicit G: Applicative[G]): G[Vector[B]] = foldRight[A, G[Vector[B]]](fa, Always(G.pure(Vector.empty))){ (a, lgvb) => G.map2Eval(f(a), lgvb)(_ +: _) diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 512edaf9e3a..f0f4b99aca1 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -10,6 +10,12 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb def iterator[T](fa: F[T]): Iterator[T] + test("size") { + forAll { (fa: F[Int]) => + fa.size should === (iterator(fa).size.toLong) + } + } + test("summation") { forAll { (fa: F[Int]) => val total = iterator(fa).sum @@ -123,6 +129,10 @@ class FoldableVectorCheck extends FoldableCheck[Vector]("vector") { def iterator[T](vector: Vector[T]): Iterator[T] = vector.iterator } +class FoldableSetCheck extends FoldableCheck[Set]("set") { + def iterator[T](set: Set[T]): Iterator[T] = set.iterator +} + class FoldableStreamCheck extends FoldableCheck[Stream]("stream") { def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator } diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 569535205f3..5c166001ac8 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -64,6 +64,12 @@ class OneAndTests extends CatsSuite { checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) + test("Foldable[OneAnd[Vector, ?]]#size consistent with Vector#size") { + forAll { (oa: OneAnd[Vector, Int]) => + oa.size should === (1L + oa.tail.size.toLong) + } + } + test("Show is not empty and is formatted as expected") { forAll { (nel: NonEmptyList[Int]) => nel.show.nonEmpty should === (true)