Skip to content

Commit aa348c0

Browse files
committed
Merge pull request #925 from TomasMikula/foldM
Stack-safe foldM
2 parents 3f48848 + 5146e59 commit aa348c0

2 files changed

Lines changed: 22 additions & 0 deletions

File tree

core/src/main/scala/cats/Foldable.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ import simulacrum.typeclass
7676
def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B =
7777
foldLeft(fa, B.empty)((b, a) => B.combine(b, f(a)))
7878

79+
/**
80+
* Left associative monadic folding on `F`.
81+
*/
82+
def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] =
83+
foldLeft(fa, G.pure(z))((gb, a) => G.flatMap(gb)(f(_, a)))
84+
7985
/**
8086
* Traverse `F[A]` using `Applicative[G]`.
8187
*

tests/src/test/scala/cats/tests/FoldableTests.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class FoldableTestsAdditional extends CatsSuite {
6363
// more basic checks
6464
val names = List("Aaron", "Betty", "Calvin", "Deirdra")
6565
F.foldMap(names)(_.length) should === (names.map(_.length).sum)
66+
val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) }
67+
assert(sumM == Some("AaronBettyCalvinDeirdra"))
68+
val notCalvin = F.foldM(names, "") { (acc, x) =>
69+
if (x == "Calvin") (None: Option[String])
70+
else (Some(acc + x): Option[String]) }
71+
assert(notCalvin == None)
6672

6773
// test trampolining
6874
val large = (1 to 10000).toList
@@ -73,6 +79,16 @@ class FoldableTestsAdditional extends CatsSuite {
7379
larger.value should === (large.map(_ + 1))
7480
}
7581

82+
test("Foldable[List].foldM stack safety") {
83+
def nonzero(acc: Long, x: Long): Option[Long] =
84+
if (x == 0) None else Some(acc + x)
85+
86+
val n = 100000L
87+
val expected = n*(n+1)/2
88+
val actual = Foldable[List].foldM((1L to n).toList, 0L)(nonzero)
89+
assert(actual.get == expected)
90+
}
91+
7692
test("Foldable[Stream]") {
7793
val F = Foldable[Stream]
7894

0 commit comments

Comments
 (0)