From 926aed69d3087995c7d4864b7ac05a04a0b4c9e9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 18 Jun 2015 22:39:28 -0400 Subject: [PATCH 1/4] Add foldM --- core/src/main/scala/cats/Foldable.scala | 11 +++++++++++ tests/src/test/scala/cats/tests/FoldableTests.scala | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 676c80f5ac..f992b068b1 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -76,6 +76,17 @@ import simulacrum.typeclass def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B = foldLeft(fa, B.empty)((b, a) => B.combine(b, f(a))) + /** + * Left associative monadic folding on `F`. + */ + def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B]) + (implicit G: Monad[G]): G[B] = + partialFold[A, B => G[B]](fa)({a: A => + Fold.Continue({ b => + (w: B) => G.flatMap(f(w, a))(b) + }) + }).complete(Lazy { b: B => G.pure(b) })(z) + /** * Traverse `F[A]` using `Applicative[G]`. * diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 8d4efefd24..a4976f60a7 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -63,6 +63,12 @@ class FoldableTestsAdditional extends CatsSuite { // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") F.foldMap(names)(_.length) should === (names.map(_.length).sum) + val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) } + assert(sumM == Some("AaronBettyCalvinDeirdra")) + val notCalvin = F.foldM(names, "") { (acc, x) => + if (x == "Calvin") (None: Option[String]) + else (Some(acc + x): Option[String]) } + assert(notCalvin == None) // test trampolining val large = (1 to 10000).toList From 651046d4c661336f38ea204be7784445c78c83e1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 2 Jan 2016 23:14:12 -0500 Subject: [PATCH 2/4] Rewrote to use foldRight --- core/src/main/scala/cats/Foldable.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index f992b068b1..1b0cb490cb 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -79,13 +79,15 @@ import simulacrum.typeclass /** * Left associative monadic folding on `F`. */ - def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B]) - (implicit G: Monad[G]): G[B] = - partialFold[A, B => G[B]](fa)({a: A => - Fold.Continue({ b => - (w: B) => G.flatMap(f(w, a))(b) - }) - }).complete(Lazy { b: B => G.pure(b) })(z) + def foldM[G[_], A, B](fa: F[A], z: Eval[B])(f: (B, A) => G[B]) + (implicit G: Monad[G]): Eval[G[B]] = + foldRight[A, B => G[B]](fa, Eval.later { G.pure _ }) { (a: A, acc: Eval[B => G[B]]) => + acc map { (k: B => G[B]) => + w: B => G.flatMap(f(w, a))(k) + } + } flatMap { acc => + z map { b: B => acc(b) } + } /** * Traverse `F[A]` using `Applicative[G]`. From 6bbb05c3b4684d01416dd4101a0565428fde0884 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 2 Jan 2016 23:21:29 -0500 Subject: [PATCH 3/4] Fix test --- tests/src/test/scala/cats/tests/FoldableTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index a4976f60a7..de91808d69 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -63,9 +63,9 @@ class FoldableTestsAdditional extends CatsSuite { // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") F.foldMap(names)(_.length) should === (names.map(_.length).sum) - val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) } + val sumM = F.foldM(names, Eval.later { "" }) { (acc, x) => (Some(acc + x): Option[String]) } assert(sumM == Some("AaronBettyCalvinDeirdra")) - val notCalvin = F.foldM(names, "") { (acc, x) => + val notCalvin = F.foldM(names, Eval.later { "" }) { (acc, x) => if (x == "Calvin") (None: Option[String]) else (Some(acc + x): Option[String]) } assert(notCalvin == None) From 5146e59cb7e75deb0e31432cc3e90ca0cd5943f1 Mon Sep 17 00:00:00 2001 From: Tomas Mikula Date: Thu, 10 Mar 2016 15:29:14 -0500 Subject: [PATCH 4/4] Fix stack-safety of foldM. --- core/src/main/scala/cats/Foldable.scala | 11 ++--------- .../src/test/scala/cats/tests/FoldableTests.scala | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 1b0cb490cb..aa83ebf49e 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -79,15 +79,8 @@ import simulacrum.typeclass /** * Left associative monadic folding on `F`. */ - def foldM[G[_], A, B](fa: F[A], z: Eval[B])(f: (B, A) => G[B]) - (implicit G: Monad[G]): Eval[G[B]] = - foldRight[A, B => G[B]](fa, Eval.later { G.pure _ }) { (a: A, acc: Eval[B => G[B]]) => - acc map { (k: B => G[B]) => - w: B => G.flatMap(f(w, a))(k) - } - } flatMap { acc => - z map { b: B => acc(b) } - } + def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = + foldLeft(fa, G.pure(z))((gb, a) => G.flatMap(gb)(f(_, a))) /** * Traverse `F[A]` using `Applicative[G]`. diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index de91808d69..fda51a443a 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -63,9 +63,9 @@ class FoldableTestsAdditional extends CatsSuite { // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") F.foldMap(names)(_.length) should === (names.map(_.length).sum) - val sumM = F.foldM(names, Eval.later { "" }) { (acc, x) => (Some(acc + x): Option[String]) } + val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) } assert(sumM == Some("AaronBettyCalvinDeirdra")) - val notCalvin = F.foldM(names, Eval.later { "" }) { (acc, x) => + val notCalvin = F.foldM(names, "") { (acc, x) => if (x == "Calvin") (None: Option[String]) else (Some(acc + x): Option[String]) } assert(notCalvin == None) @@ -79,6 +79,16 @@ class FoldableTestsAdditional extends CatsSuite { larger.value should === (large.map(_ + 1)) } + test("Foldable[List].foldM 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 = Foldable[List].foldM((1L to n).toList, 0L)(nonzero) + assert(actual.get == expected) + } + test("Foldable[Stream]") { val F = Foldable[Stream]