From 6f9b8cceeeada6f8dae46ed5dce402379361af1b Mon Sep 17 00:00:00 2001 From: Matthias Berndt Date: Thu, 7 Nov 2019 19:51:48 +0100 Subject: [PATCH] add foldMapA to Foldable and add a test for sufficient short-circuiting --- core/src/main/scala/cats/Foldable.scala | 8 ++++++++ tests/src/test/scala/cats/tests/FoldableSuite.scala | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 0e2d8b69e5..398f61607e 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -333,6 +333,14 @@ import Foldable.sentinel def foldMapM[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Monad[G], B: Monoid[B]): G[B] = foldM(fa, B.empty)((b, a) => G.map(f(a))(B.combine(b, _))) + /** + * Equivalent to foldMapM. + * The difference is that foldMapA only requires G to be an Applicative + * rather than a Monad. It is also slower due to use of Eval. + */ + def foldMapA[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Applicative[G], B: Monoid[B]): G[B] = + foldRight(fa, Eval.now(G.pure(B.empty)))((a, egb) => G.map2Eval(f(a), egb)(B.combine)).value + /** * Traverse `F[A]` using `Applicative[G]`. * diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index addf449232..ffa6654929 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -280,6 +280,18 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl (Some(x): Option[String]) } assert(sumMapM == Some("AaronBettyCalvinDeirdra")) + + // foldMapM should short-circuit and not call the function when not necessary + val f = (_: String) match { + case "Calvin" => None + case "Deirdra" => + fail: Unit // : Unit ascription suppresses unreachable code warning + None + case x => Some(x) + } + names.foldMapM(f) + names.foldMapA(f) + val isNotCalvin: String => Option[String] = x => if (x == "Calvin") None else Some(x) val notCalvin = F.foldM(names, "") { (acc, x) =>