diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 0d81c48eb8..65c0630ebb 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -3,6 +3,7 @@ package cats import cats.Foldable.sentinel import cats.instances.either._ import cats.kernel.CommutativeMonoid +import cats.syntax.foldable import simulacrum.typeclass import scala.collection.mutable @@ -239,9 +240,7 @@ import scala.collection.mutable * Fold implemented using the given Monoid[A] instance. */ def fold[A](fa: F[A])(implicit A: Monoid[A]): A = - foldLeft(fa, A.empty) { (acc, a) => - A.combine(acc, a) - } + A.combineAll(foldable.catsSyntaxFoldableOps0(fa).toIterable(this)) /** * Alias for [[fold]]. diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 4fdf6f86e3..322a4594e7 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -332,6 +332,18 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { def maximumByOption[B: Order](f: A => B)(implicit F: Foldable[F]): Option[A] = F.maximumOption(fa)(Order.by(f)) + def combineAllOption(implicit ev: Semigroup[A], F: Foldable[F]): Option[A] = + if (F.isEmpty(fa)) None else ev.combineAllOption(toIterable) + + /** + * Convert F[A] to an Iterable[A]. + * + * This method may be overridden for the sake of performance, but implementers should take care + * not to force a full materialization of the collection. + */ + def toIterable(implicit F: Foldable[F]): Iterable[A] = + F.foldRight[A, Stream[A]](fa, Eval.now(Stream.empty))((a, eb) => eb.map(Stream.cons(a, _))).value + /** * Implementers are responsible for ensuring they maintain consistency with foldRight; this is not checked by laws on Scala 2.11 */ diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index 6f25d19db8..f3fd05e362 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -214,6 +214,20 @@ abstract class FoldableSuite[F[_]: Foldable](name: String)(implicit ArbFInt: Arb } } + test(s"Foldable[$name].combineAllOption") { + forAll { (fa: F[Int]) => + fa.combineAllOption should ===(fa.toList.combineAllOption) + fa.combineAllOption should ===(iterator(fa).toList.combineAllOption) + } + } + + test(s"Foldable[$name].iterable") { + forAll { (fa: F[Int]) => + fa.toIterable.toList should ===(fa.toList) + fa.toIterable.toList should ===(iterator(fa).toList) + } + } + test(s"Foldable[$name].intercalate") { forAll { (fa: F[String], a: String) => fa.intercalate(a) should ===(fa.toList.mkString(a))