diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index fb6cfe1c71..e2b11d813b 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -59,6 +59,12 @@ import simulacrum.typeclass */ def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B + /** + * Monadic variant of [[reduceLeftTo]] + */ + def reduceLeftM[G[_], A, B](fa: F[A])(f: A => G[B])(g: (B, A) => G[B])(implicit G: FlatMap[G]): G[B] = + reduceLeftTo(fa)(f)((gb, a) => G.flatMap(gb)(g(_, a))) + /** * Overriden from Foldable[_] for efficiency. */ diff --git a/tests/src/test/scala/cats/tests/ReducibleTests.scala b/tests/src/test/scala/cats/tests/ReducibleTests.scala new file mode 100644 index 0000000000..ede3ac68b9 --- /dev/null +++ b/tests/src/test/scala/cats/tests/ReducibleTests.scala @@ -0,0 +1,18 @@ +package cats +package tests + +class ReducibleTestsAdditional extends CatsSuite { + + test("Reducible[NonEmptyList].reduceLeftM stack safety") { + def nonzero(acc: Long, x: Long): Option[Long] = + if (x == 0) None else Some(acc + x) + + val n = 100000L + val expected = n*(n+1)/2 + val actual = (1L to n).toList.toNel.flatMap(_.reduceLeftM(Option.apply)(nonzero)) + actual should === (Some(expected)) + } + +} + +