From f064933a3dceb56f24ca92dff07ccd1befff436d Mon Sep 17 00:00:00 2001 From: Denis Rosca Date: Fri, 29 Mar 2019 16:08:14 +0000 Subject: [PATCH 1/6] Add Foldable.foldRightDefer --- core/src/main/scala/cats/Foldable.scala | 14 ++++++++++++++ laws/src/main/scala/cats/laws/FoldableLaws.scala | 11 +++++++++++ .../scala/cats/laws/discipline/FoldableTests.scala | 1 + .../src/test/scala/cats/tests/FoldableSuite.scala | 7 +++++++ 4 files changed, 33 insertions(+) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index f425611302..e385bb78a3 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -95,6 +95,13 @@ import Foldable.sentinel */ def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] + def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = + Defer[G].defer( + this.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => + Defer[G].defer(fn(elem, acc(z))) + }(gb) + ) + def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] = foldLeft(fa, Option.empty[B]) { case (Some(b), a) => Some(g(b, a)) @@ -594,6 +601,13 @@ object Foldable { Eval.always(iterable.iterator).flatMap(loop) } + def iterateRightDefer[G[_]: Defer, A, B](iterable: Iterable[A], lb: G[B])(f: (A, G[B]) => G[B]): G[B] = { + def loop(it: Iterator[A]): G[B] = + Defer[G].defer(if (it.hasNext) f(it.next(), loop(it)) else Defer[G].defer(lb)) + + loop(iterable.iterator) + } + /** * Isomorphic to * diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 64cb6e83ca..e5960b2585 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -37,6 +37,16 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] { ): IsEq[B] = F.foldM[Id, A, B](fa, b)(f) <-> F.foldLeft(fa, b)(f) + def foldRightDeferConsistentWithFoldRight[A, B]( + fa: F[A], + f: (B, A) => B + )(implicit + M: Monoid[B]): IsEq[B] = { + val g: (A, Eval[B]) => Eval[B] = (a, ea) => ea.map(f(_, a)) + + F.foldRight(fa, Later(M.empty))(g).value <-> F.foldRightDefer(fa, Later(M.empty): Eval[B])(g).value + } + /** * `reduceLeftOption` consistent with `reduceLeftToOption` */ @@ -111,6 +121,7 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] { def orderedConsistency[A: Eq](x: F[A], y: F[A])(implicit ev: Eq[F[A]]): IsEq[List[A]] = if (x === y) (F.toList(x) <-> F.toList(y)) else List.empty[A] <-> List.empty[A] + } object FoldableLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 65d10b2081..ef690fcc31 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -44,6 +44,7 @@ trait FoldableTests[F[_]] extends UnorderedFoldableTests[F] { "dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _), "collectFirstSome reference" -> forAll(laws.collectFirstSome_Ref[A, B] _), "collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _) +// "foldRightDefer consistency" -> forAll(laws.foldRightDeferConsistentWithFoldRight[A, B] _) ) } diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index 1986caa511..f0bdc46622 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -282,6 +282,13 @@ class FoldableSuiteAdditional extends CatsSuite { // safely build large lists val larger = F.foldRight(large, Now(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _)) larger.value should ===(large.map(_ + 1)) + + val sum = F.foldRightDefer(large, Eval.later(0))((elem, acc) => acc.map(_ + elem)) + sum.value should ===(large.sum) + + def boom[A]: Eval[A] = Eval.later(sys.error("boom")) + // Ensure that the lazy param is actually handled lazily + val lazySum: Eval[Int] = F.foldRightDefer(large, boom[Int])((elem, acc) => acc.map(_ + elem)) } def checkMonadicFoldsStackSafety[F[_]](fromRange: Range => F[Int])(implicit F: Foldable[F]): Unit = { From e2a5f5259f15dff24e63297f8bf06db686cb8d60 Mon Sep 17 00:00:00 2001 From: Denis Rosca Date: Wed, 3 Apr 2019 17:22:14 +0100 Subject: [PATCH 2/6] Fix foldRightDefer <-> foldRight consistency --- core/src/main/scala/cats/Foldable.scala | 2 +- laws/src/main/scala/cats/laws/discipline/FoldableTests.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index e385bb78a3..6bb6af5eae 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -98,7 +98,7 @@ import Foldable.sentinel def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = Defer[G].defer( this.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => - Defer[G].defer(fn(elem, acc(z))) + Defer[G].defer(acc(fn(elem, z))) }(gb) ) diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index ef690fcc31..b2c6c3fac3 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -43,8 +43,8 @@ trait FoldableTests[F[_]] extends UnorderedFoldableTests[F] { "takeWhile_ reference" -> forAll(laws.takeWhile_Ref[A] _), "dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _), "collectFirstSome reference" -> forAll(laws.collectFirstSome_Ref[A, B] _), - "collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _) -// "foldRightDefer consistency" -> forAll(laws.foldRightDeferConsistentWithFoldRight[A, B] _) + "collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _), + "foldRightDefer consistency" -> forAll(laws.foldRightDeferConsistentWithFoldRight[A, B] _) ) } From ab61a9ac046c45348ecd8b3546eee2267247ebef Mon Sep 17 00:00:00 2001 From: Denis Rosca Date: Thu, 4 Apr 2019 10:16:44 +0100 Subject: [PATCH 3/6] Make Foldable.iterateRightDefer lazy --- core/src/main/scala/cats/Foldable.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 6bb6af5eae..d2c5eb5d98 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -594,18 +594,14 @@ import Foldable.sentinel object Foldable { private val sentinel: Function1[Any, Any] = new scala.runtime.AbstractFunction1[Any, Any] { def apply(a: Any) = this } - def iterateRight[A, B](iterable: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - def loop(it: Iterator[A]): Eval[B] = - Eval.defer(if (it.hasNext) f(it.next, loop(it)) else lb) - - Eval.always(iterable.iterator).flatMap(loop) - } + def iterateRight[A, B](iterable: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + iterateRightDefer(iterable, lb)(f) def iterateRightDefer[G[_]: Defer, A, B](iterable: Iterable[A], lb: G[B])(f: (A, G[B]) => G[B]): G[B] = { def loop(it: Iterator[A]): G[B] = - Defer[G].defer(if (it.hasNext) f(it.next(), loop(it)) else Defer[G].defer(lb)) + Defer[G].defer(if (it.hasNext) f(it.next(), Defer[G].defer(loop(it))) else Defer[G].defer(lb)) - loop(iterable.iterator) + Defer[G].defer(loop(iterable.iterator)) } /** From 9528be60e40da898773c9f0a8a753fab36ad8432 Mon Sep 17 00:00:00 2001 From: Denis Rosca Date: Thu, 4 Apr 2019 17:17:56 +0100 Subject: [PATCH 4/6] Fix MiMa incompatibilities --- core/src/main/scala/cats/Foldable.scala | 7 --- .../src/main/scala/cats/syntax/foldable.scala | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index d2c5eb5d98..90f33e365b 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -95,13 +95,6 @@ import Foldable.sentinel */ def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] - def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = - Defer[G].defer( - this.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => - Defer[G].defer(acc(fn(elem, z))) - }(gb) - ) - def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] = foldLeft(fa, Option.empty[B]) { case (Some(b), a) => Some(g(b, a)) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 27a41812d4..9cb9814349 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -303,6 +303,31 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { import cats.syntax.foldable._ F.partitionEitherM[G, A, B, C](fa)(f)(A, M) } + + /** + * Right associative lazy fold on `F` using the folding function 'f' + * provided that `G[_]` has a `Defer[G]` instance in scope. + * + * For more detailed information about how this method works see the + * documentation for `Defer[F]`. + * + * Example: + * {{{ + * scala> import cats.Eval, cats.implicits._ + * scala> val fa = Option(1) + * + * Folding by addition to zero: + * With syntax extensions, we can write the same thing like this: + * scala> val folded2 = fa.foldRightDefer(Eval.now(0))((n, a) => a.map(_ + n)) + * scala> folded2.value + * res1: Int = 1 + * }}} + */ + def foldRightDefer[G[_]: Defer, B](gb: G[B])(fn: (A, G[B]) => G[B])(implicit F: Foldable[F]): G[B] = { + import cats.syntax.foldable._ + F.foldRightDefer(fa, gb)(fn) + } + } final class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal { @@ -381,4 +406,30 @@ final class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal { import cats.instances.either._ partitionBifoldM[G, Either, A, B, C](fa)(f)(A, M, Bifoldable[Either]) } + + /** + * Right associative lazy fold on `F` using the folding function 'f' + * provided that `G[_]` has a `Defer[G]` instance in scope. + * + * For more detailed information about how this method works see the + * documentation for `Defer[F]`. + * + * Example: + * {{{ + * scala> import cats.Foldable, cats.Eval, cats.implicits._ + * scala> val fa = Option(1) + * + * Folding by addition to zero: + * scala> val folded1 = Foldable[Option].foldRightDefer(fa, Eval.now(0))((n, a) => a.map(_ + n)) + * Since `foldRightDefer` yields a lazy computation, we need to force it to inspect the result: + * scala> folded1.value + * res0: Int = 1 + * }}} + */ + def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = + Defer[G].defer( + F.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => + Defer[G].defer(acc(fn(elem, z))) + }(gb) + ) } From bfddd53e146500360419f03578708c25a4411454 Mon Sep 17 00:00:00 2001 From: Denis Rosca Date: Fri, 5 Apr 2019 09:30:33 +0100 Subject: [PATCH 5/6] Revert "Fix MiMa incompatibilities" This reverts commit 9528be60e40da898773c9f0a8a753fab36ad8432. --- core/src/main/scala/cats/Foldable.scala | 7 +++ .../src/main/scala/cats/syntax/foldable.scala | 51 ------------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 90f33e365b..d2c5eb5d98 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -95,6 +95,13 @@ import Foldable.sentinel */ def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] + def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = + Defer[G].defer( + this.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => + Defer[G].defer(acc(fn(elem, z))) + }(gb) + ) + def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] = foldLeft(fa, Option.empty[B]) { case (Some(b), a) => Some(g(b, a)) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 9cb9814349..27a41812d4 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -303,31 +303,6 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal { import cats.syntax.foldable._ F.partitionEitherM[G, A, B, C](fa)(f)(A, M) } - - /** - * Right associative lazy fold on `F` using the folding function 'f' - * provided that `G[_]` has a `Defer[G]` instance in scope. - * - * For more detailed information about how this method works see the - * documentation for `Defer[F]`. - * - * Example: - * {{{ - * scala> import cats.Eval, cats.implicits._ - * scala> val fa = Option(1) - * - * Folding by addition to zero: - * With syntax extensions, we can write the same thing like this: - * scala> val folded2 = fa.foldRightDefer(Eval.now(0))((n, a) => a.map(_ + n)) - * scala> folded2.value - * res1: Int = 1 - * }}} - */ - def foldRightDefer[G[_]: Defer, B](gb: G[B])(fn: (A, G[B]) => G[B])(implicit F: Foldable[F]): G[B] = { - import cats.syntax.foldable._ - F.foldRightDefer(fa, gb)(fn) - } - } final class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal { @@ -406,30 +381,4 @@ final class FoldableOps1[F[_]](private val F: Foldable[F]) extends AnyVal { import cats.instances.either._ partitionBifoldM[G, Either, A, B, C](fa)(f)(A, M, Bifoldable[Either]) } - - /** - * Right associative lazy fold on `F` using the folding function 'f' - * provided that `G[_]` has a `Defer[G]` instance in scope. - * - * For more detailed information about how this method works see the - * documentation for `Defer[F]`. - * - * Example: - * {{{ - * scala> import cats.Foldable, cats.Eval, cats.implicits._ - * scala> val fa = Option(1) - * - * Folding by addition to zero: - * scala> val folded1 = Foldable[Option].foldRightDefer(fa, Eval.now(0))((n, a) => a.map(_ + n)) - * Since `foldRightDefer` yields a lazy computation, we need to force it to inspect the result: - * scala> folded1.value - * res0: Int = 1 - * }}} - */ - def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = - Defer[G].defer( - F.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => - Defer[G].defer(acc(fn(elem, z))) - }(gb) - ) } From 1cd271483e99b143fdf63f3287f9a9502abdb868 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Tue, 12 Nov 2019 06:52:12 -0600 Subject: [PATCH 6/6] Change back iterateRight implementation to use Eval directly --- core/src/main/scala/cats/Foldable.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 4d956544d0..5b672a7d3d 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -630,8 +630,12 @@ import Foldable.sentinel object Foldable { private val sentinel: Function1[Any, Any] = new scala.runtime.AbstractFunction1[Any, Any] { def apply(a: Any) = this } - def iterateRight[A, B](iterable: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - iterateRightDefer(iterable, lb)(f) + def iterateRight[A, B](iterable: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(it: Iterator[A]): Eval[B] = + Eval.defer(if (it.hasNext) f(it.next, loop(it)) else lb) + + Eval.always(iterable.iterator).flatMap(loop) + } def iterateRightDefer[G[_]: Defer, A, B](iterable: Iterable[A], lb: G[B])(f: (A, G[B]) => G[B]): G[B] = { def loop(it: Iterator[A]): G[B] =