diff --git a/core/src/main/scala/cats/derived/foldable.scala b/core/src/main/scala/cats/derived/foldable.scala index 82f57df0..b28cc2f1 100644 --- a/core/src/main/scala/cats/derived/foldable.scala +++ b/core/src/main/scala/cats/derived/foldable.scala @@ -16,114 +16,98 @@ package cats.derived -import cats.{ Eval, Foldable }, Eval.now +import cats.{Eval, Foldable} import shapeless._ +import scala.annotation.implicitNotFound + +@implicitNotFound("Could not derive an instance of Foldable[${F}]") trait MkFoldable[F[_]] extends Foldable[F] { - def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = safeFoldLeft(fa, b){ (b, a) => now(f(b, a)) }.value def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] + + def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = + safeFoldLeft(fa, b)((b, a) => Eval.later(f(b, a))).value } object MkFoldable extends MkFoldableDerivation { - def apply[F[_]](implicit mff: MkFoldable[F]): MkFoldable[F] = mff + def apply[F[_]](implicit F: MkFoldable[F]): MkFoldable[F] = F } -trait MkFoldableDerivation extends MkFoldable0 { - implicit val mkFoldableId: MkFoldable[shapeless.Id] = - new MkFoldable[shapeless.Id] { - def foldRight[A, B](fa: A, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = f(fa, lb) - def safeFoldLeft[A, B](fa: A, b: B)(f: (B, A) => Eval[B]): Eval[B] = now(f(b, fa).value) - } +private[derived] abstract class MkFoldableDerivation extends MkFoldableNested { + implicit val mkFoldableHNil: MkFoldable[Const[HNil]#λ] = mkFoldableConst + implicit val mkFoldableCNil: MkFoldable[Const[CNil]#λ] = mkFoldableConst - override implicit def mkFoldableConstFoldable[T]: MkFoldable[Const[T]#λ] = - super[MkFoldable0].mkFoldableConstFoldable + implicit def mkFoldableConst[T]: MkFoldable[Const[T]#λ] = + new MkFoldable[Const[T]#λ] { + def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = lb + def safeFoldLeft[A, B](fa: T, b: B)(f: (B, A) => Eval[B]) = Eval.now(b) + } } -trait MkFoldable0 extends MkFoldable1 { - // Induction step for products - implicit def mkFoldableHcons[F[_]](implicit ihc: IsHCons1[F, Foldable, MkFoldable]): MkFoldable[F] = - new MkFoldable[F] { - def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - import ihc._ - val (hd, tl) = unpack(fa) - for { - t <- ft.foldRight(tl, lb)(f) - h <- fh.foldRight(hd, now(t))(f) - } yield h - } - - def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = { - import ihc._ - val (hd, tl) = unpack(fa) - for { - h <- fh.safeFoldLeft(hd, b)(f) - t <- ft.safeFoldLeft(tl, h)(f) - } yield t - } - } +private[derived] abstract class MkFoldableNested extends MkFoldableCons { - // Induction step for coproducts - implicit def mkFoldableCcons[F[_]](implicit icc: IsCCons1[F, Foldable, MkFoldable]): MkFoldable[F] = + implicit def mkFoldableNested[F[_]](implicit F: Split1[F, FoldableOrMk, FoldableOrMk]): MkFoldable[F] = new MkFoldable[F] { - def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - import icc._ - unpack(fa) match { - case Left(hd) => fh.foldRight(hd, lb)(f) - case Right(tl) => ft.foldRight(tl, lb)(f) - } - } - def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = { - import icc._ - unpack(fa) match { - case Left(hd) => fh.safeFoldLeft(hd, b)(f) - case Right(tl) => ft.safeFoldLeft(tl, b)(f) - } - } + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = + F.fo.unify.foldRight(F.unpack(fa), lb)((fia, lb) => Eval.defer(F.fi.unify.foldRight(fia, lb)(f))) + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]) = + mkSafeFoldLeft(F.fo)(F.unpack(fa), b)((b, fia) => mkSafeFoldLeft(F.fi)(fia, b)(f)) } } -trait MkFoldable1 extends MkFoldable2 { - implicit def mkFoldableSplit[F[_]](implicit split: Split1[F, Foldable, Foldable]): MkFoldable[F] = +private[derived] abstract class MkFoldableCons extends MkFoldableGeneric { + + implicit def mkFoldableHCons[F[_]](implicit F: IsHCons1[F, FoldableOrMk, MkFoldable]): MkFoldable[F] = new MkFoldable[F] { - def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - import split._ - fo.foldRight(unpack(fa), lb) { (fai, lbi) => fi.foldRight(fai, lbi)(f) } - } - - def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = { - import split._ - fo.safeFoldLeft(unpack(fa), b){ (lbi, fai) => fi.safeFoldLeft(fai, lbi)(f) } - } + + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = for { + unpacked <- Eval.now(F.unpack(fa)) + b <- F.fh.unify.foldRight(unpacked._1, F.ft.foldRight(unpacked._2, lb)(f))(f) + } yield b + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]) = for { + unpacked <- Eval.now(F.unpack(fa)) + hb <- mkSafeFoldLeft(F.fh)(unpacked._1, b)(f) + tb <- F.ft.safeFoldLeft(unpacked._2, hb)(f) + } yield tb } -} -trait MkFoldable2 extends MkFoldable3 { - implicit def mkFoldableGeneric[F[_]](implicit gen: Generic1[F, MkFoldable]): MkFoldable[F] = + implicit def mkFoldableCCons[F[_]](implicit F: IsCCons1[F, FoldableOrMk, MkFoldable]): MkFoldable[F] = new MkFoldable[F] { - def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - gen.fr.foldRight(gen.to(fa), lb)(f) - def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = - gen.fr.safeFoldLeft(gen.to(fa), b)(f) + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = + F.unpack(fa) match { + case Left(fha) => F.fh.unify.foldRight(fha, lb)(f) + case Right(fta) => F.ft.foldRight(fta, lb)(f) + } + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]) = + F.unpack(fa) match { + case Left(fha) => mkSafeFoldLeft(F.fh)(fha, b)(f) + case Right(fta) => F.ft.safeFoldLeft(fta, b)(f) + } } } -trait MkFoldable3 { +private[derived] abstract class MkFoldableGeneric { + protected type FoldableOrMk[F[_]] = Foldable[F] OrElse MkFoldable[F] - // For binary compatibility. - def mkFoldableConstFoldable[T]: MkFoldable[Const[T]#λ] = - new MkFoldable[Const[T]#λ] { - def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb - def safeFoldLeft[A, B](fa: T, b: B)(f: (B, A) => Eval[B]): Eval[B] = now(b) + protected def mkSafeFoldLeft[F[_], A, B](F: FoldableOrMk[F])(fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = + F.unify match { + case mk: MkFoldable[F] => mk.safeFoldLeft(fa, b)(f) + case other => Eval.later(other.foldLeft(fa, b)(f(_, _).value)) } - implicit class FoldableSafeFoldLeft[F[_]](val ff: Foldable[F]) { - def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = - ff match { - case mff: MkFoldable[F] => mff.safeFoldLeft(fa, b)(f) - case _ => now(ff.foldLeft(fa, b) { (b, a) => f(b, a).value }) - } - } + implicit def mkFoldableGeneric[F[_]](implicit F: Generic1[F, MkFoldable]): MkFoldable[F] = + new MkFoldable[F] { + + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = + F.fr.foldRight(F.to(fa), lb)(f) + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]) = + F.fr.safeFoldLeft(F.to(fa), b)(f) + } } diff --git a/core/src/main/scala/cats/derived/package.scala b/core/src/main/scala/cats/derived/package.scala index 6587bc18..659344af 100644 --- a/core/src/main/scala/cats/derived/package.scala +++ b/core/src/main/scala/cats/derived/package.scala @@ -97,6 +97,12 @@ object auto { ): MonoidK[F] = ev } + object foldable { + implicit def kittensMkFoldable[F[_]]( + implicit refute: Refute[Foldable[F]], F: Lazy[MkFoldable[F]] + ): Foldable[F] = F.value + } + object traverse { implicit def kittensMkTraverse[F[_]]( implicit refute: Refute[Traverse[F]], F: Lazy[MkTraverse[F]] @@ -105,7 +111,6 @@ object auto { //todo: the regular approach doesn't work for the following instances object pure extends MkPureDerivation - object foldable extends MkFoldableDerivation object consK { implicit def kittensMkConsK[F[_]]( @@ -159,8 +164,8 @@ object cached { object foldable { implicit def kittensMkFoldable[F[_]]( - implicit refute: Refute[Foldable[F]], ev: Cached[MkFoldable[F]]) - : Foldable[F] = ev.value + implicit refute: Refute[Foldable[F]], cached: Cached[MkFoldable[F]] + ): Foldable[F] = cached.value } object traverse{ @@ -264,7 +269,7 @@ object semi { def showPretty[A](implicit ev: MkShowPretty[A]): ShowPretty[A] = ev - def foldable[F[_]](implicit F: MkFoldable[F]): Foldable[F] = F + def foldable[F[_]](implicit F: Lazy[MkFoldable[F]]): Foldable[F] = F.value def traverse[F[_]](implicit F: Lazy[MkTraverse[F]]): Traverse[F] = F.value diff --git a/core/src/test/scala/cats/derived/foldable.scala b/core/src/test/scala/cats/derived/foldable.scala index c910acad..bcac38ec 100644 --- a/core/src/test/scala/cats/derived/foldable.scala +++ b/core/src/test/scala/cats/derived/foldable.scala @@ -14,69 +14,116 @@ * limitations under the License. */ -package cats.derived +package cats +package derived -import cats.{ Eq, Eval, Foldable }, Eval.now +import cats.instances.all._ +import cats.laws.discipline.FoldableTests +import org.scalacheck.Arbitrary -import TestDefns._ class FoldableSuite extends KittensSuite { - // disable scalatest === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? + import FoldableSuite._ + import TestDefns._ - // exists method written in terms of foldRight - def contains[F[_]: Foldable, A: Eq](as: F[A], goal: A): Eval[Boolean] = - as.foldRight(now(false)) { (a, lb) => - if (a === goal) now(true) else lb + type OptList[A] = Option[List[A]] + type ListSnoc[A] = List[Snoc[A]] + type AndChar[A] = (A, Char) + type BoxNel[A] = Box[Nel[A]] + + def testFoldable(context: String)( + implicit iList: Foldable[IList], + tree: Foldable[Tree], + genericAdt: Foldable[GenericAdt], + optList: Foldable[OptList], + listSnoc: Foldable[ListSnoc], + andChar: Foldable[AndChar], + interleaved: Foldable[Interleaved], + boxNel: Foldable[BoxNel] + ): Unit = { + checkAll(s"$context.Foldable[IList]", FoldableTests[IList].foldable[Int, Long]) + checkAll(s"$context.Foldable[Tree]", FoldableTests[Tree].foldable[Int, Long]) + checkAll(s"$context.Foldable[GenericAdt]", FoldableTests[GenericAdt].foldable[Int, Long]) + checkAll(s"$context.Foldable[OptList]", FoldableTests[OptList].foldable[Int, Long]) + checkAll(s"$context.Foldable[ListSnoc]", FoldableTests[ListSnoc].foldable[Int, Long]) + checkAll(s"$context.Foldable[AndChar]", FoldableTests[AndChar].foldable[Int, Long]) + checkAll(s"$context.Foldable[Interleaved]", FoldableTests[Interleaved].foldable[Int, Long]) + checkAll(s"$context.Foldable[BoxNel]]", FoldableTests[BoxNel].foldable[Int, Long]) + + val n = 10000 + val largeIList = IList.fromSeq(1 until n) + val largeSnoc = Snoc.fromSeq(1 until n) :: Nil + + test(s"$context.Traverse.foldLeft is stack safe") { + val actualIList = largeIList.foldLeft(0)(_ + _) + val actualSnoc = listSnoc.foldLeft(largeSnoc, 0)(_ + _) + val expected = n * (n - 1) / 2 + assert(actualIList == expected) + assert(actualSnoc == expected) + } + + test(s"$context.Traverse.foldRight is stack safe") { + val actualIList = largeIList.foldRight(Eval.Zero)((i, sum) => sum.map(_ + i)) + val actualSnoc = listSnoc.foldRight(largeSnoc, Eval.Zero)((i, sum) => sum.map(_ + i)) + val expected = n * (n - 1) / 2 + assert(actualIList.value == expected) + assert(actualSnoc.value == expected) } - import auto.foldable._ - import cats.instances.int._ - - test("Foldable[IList]") { - val F = Foldable[IList] - - // some basic sanity checks - val lns = (1 to 10).toList - val ns = IList.fromSeq(lns) - val total = lns.sum - assert(F.foldLeft(ns, 0)(_ + _) == total) - assert(F.foldRight(ns, now(0))((x, ly) => ly.map(x + _)).value == total) - assert(F.fold(ns) == total) - - // more basic checks - val lnames = List("Aaron", "Betty", "Calvin", "Deirdra") - val names = IList.fromSeq(lnames) - assert(F.foldMap(names)(_.length) == lnames.map(_.length).sum) - - // test trampolining - val llarge = 1 to 10000 - val large = IList.fromSeq(llarge) - val largeTotal = llarge.sum - assert(F.foldLeft(large, 0)(_ + _) == largeTotal) - assert(F.fold(large) == largeTotal) - assert(contains(large, 10000).value) - - // safely build large lists - val larger = F.foldRight(large, now(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _)) - assert(larger.value == llarge.map(_ + 1)) + test(s"$context.Foldable respects existing instances") { + val tail = List.range(1, 100) + val sum = boxNel.fold(Box(Nel(42, tail))) + assert(sum == tail.sum) + } + } + + { + import auto.foldable._ + testFoldable("auto") + } + + { + import cached.foldable._ + testFoldable("cached") } - test("derives an instance for Interleaved[T]") { - semi.foldable[TestDefns.Interleaved] + semiTests.run() + + object semiTests { + implicit val iList: Foldable[IList] = semi.foldable + implicit val tree: Foldable[Tree] = semi.foldable + implicit val genericAdt: Foldable[GenericAdt] = semi.foldable + implicit val optList: Foldable[OptList] = semi.foldable + implicit val listSnoc: Foldable[ListSnoc] = semi.foldable + implicit val andChar: Foldable[AndChar] = semi.foldable + implicit val interleaved: Foldable[Interleaved] = semi.foldable + implicit val boxNel: Foldable[BoxNel] = semi.foldable + def run(): Unit = testFoldable("semi") } +} - test("foldable.semi[Tree]") { - val F = semi.foldable[Tree] +object FoldableSuite { - val tree: Tree[String] = - Node( - Leaf("quux"), - Node( - Leaf("foo"), - Leaf("wibble") - ) - ) + final case class Nel[+A](head: A, tail: List[A]) + object Nel { + + implicit def eqv[A](implicit A: Eq[A]): Eq[Nel[A]] = { + val listEq = Eq[List[A]] + Eq.instance((x, y) => A.eqv(x.head, y.head) && listEq.eqv(x.tail, y.tail)) + } - assert(F.foldLeft(tree, 0)(_ + _.length) == 13) + implicit def arbitrary[A: Arbitrary]: Arbitrary[Nel[A]] = + Arbitrary(for { + head <- Arbitrary.arbitrary[A] + tail <- Arbitrary.arbitrary[List[A]] + } yield Nel(head, tail)) + + implicit val foldable: Foldable[Nel] = new Foldable[Nel] { + + def foldLeft[A, B](fa: Nel[A], b: B)(f: (B, A) => B) = + Foldable[List].foldLeft(fa.tail, b)(f) + + def foldRight[A, B](fa: Nel[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]) = + Foldable[List].foldRight(fa.tail, lb)(f) + } } }