diff --git a/README.md b/README.md index 6618db47..cbc5a5bf 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Instances derivations are available for the following type classes: * `Hash` * `Functor` * `Foldable` +* `Traverse` * `Show` * `Monoid` and `MonoidK` * `Semigroup` and `SemigroupK` diff --git a/core/src/main/scala/cats/derived/package.scala b/core/src/main/scala/cats/derived/package.scala index ffe2a004..f0de022c 100644 --- a/core/src/main/scala/cats/derived/package.scala +++ b/core/src/main/scala/cats/derived/package.scala @@ -92,6 +92,7 @@ object auto { object foldable extends MkFoldableDerivation + object traverse extends MkTraverseDerivation } @@ -139,6 +140,18 @@ object cached { : Functor[F] = ev.value } + object foldable { + implicit def kittensMkFoldable[F[_]]( + implicit refute: Refute[Foldable[F]], ev: Cached[MkFoldable[F]]) + : Foldable[F] = ev.value + } + + object traverse{ + implicit def kittensMkTraverse[F[_]]( + implicit refute: Refute[Traverse[F]], ev: Cached[MkTraverse[F]]) + : Traverse[F] = ev.value + } + object show { implicit def kittensMkshow[A]( implicit refute: Refute[Show[A]], ev: Cached[MkShow[A]]) @@ -225,6 +238,8 @@ object semi { def foldable[F[_]](implicit F: MkFoldable[F]): Foldable[F] = F + def traverse[F[_]](implicit F: MkTraverse[F]): Traverse[F] = F + def monoid[T](implicit T: MkMonoid[T]): Monoid[T] = T def monoidK[F[_]](implicit F: MkMonoidK[F]): MonoidK[F] = F diff --git a/core/src/main/scala/cats/derived/traverse.scala b/core/src/main/scala/cats/derived/traverse.scala new file mode 100644 index 00000000..587be18e --- /dev/null +++ b/core/src/main/scala/cats/derived/traverse.scala @@ -0,0 +1,166 @@ +package cats.derived + +import cats.syntax.all._ +import cats.{Applicative, Eval, Now, Traverse} +import shapeless._ + +import scala.annotation.implicitNotFound + +/** + * Based on the `MkFoldable` implementation. + */ +@implicitNotFound("Could not derive an instance of Traverse[${F}]") +trait MkTraverse[F[_]] extends Traverse[F] { + + def safeTraverse[G[_] : Applicative, A, B](fa: F[A])(f: A => Eval[G[B]]): Eval[G[F[B]]] + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] + + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] + + def traverse[G[_] : Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] = + safeTraverse(fa)(a => Now(f(a))).value + + def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B = + safeFoldLeft(fa, b) { (b, a) => Now(f(b, a)) }.value + +} + +object MkTraverse extends MkTraverseDerivation { + def apply[F[_]](implicit mff: MkTraverse[F]): MkTraverse[F] = mff +} + +trait MkTraverseDerivation extends MkTraverse0 { + implicit val mkTraverseId: MkTraverse[shapeless.Id] = new MkTraverse[shapeless.Id] { + def safeTraverse[G[_] : Applicative, A, B](fa: Id[A])(f: A => Eval[G[B]]): Eval[G[Id[B]]] = f(fa) + + 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) + + } + + implicit def mkTraverseConst[T]: MkTraverse[Const[T]#λ] = new MkTraverse[Const[T]#λ] { + override def safeTraverse[G[_] : Applicative, A, B](fa: T)(f: A => Eval[G[B]]): Eval[G[T]] = + Now(fa.pure[G]) + + 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) + } +} + +private[derived] trait MkTraverse0 extends MkTraverse1 { + // Induction step for products + implicit def mkTraverseHcons[F[_]](implicit ihc: IsHCons1[F, TraverseOrMk, MkTraverse]): MkTraverse[F] = + new MkTraverse[F] { + override def safeTraverse[G[_] : Applicative, A, B](fa: F[A])(f: A => Eval[G[B]]): Eval[G[F[B]]] = { + for { + ht <- Now(ihc.unpack(fa)) + th <- ihc.fh.unify.safeTraverse(ht._1)(f) + tt <- ihc.ft.safeTraverse(ht._2)(f) + } yield (th, tt).mapN(ihc.pack(_, _)) + } + + 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.unify.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.unify.safeFoldLeft(hd, b)(f) + t <- ft.safeFoldLeft(tl, h)(f) + } yield t + } + } + + // Induction step for coproducts + implicit def mkTraverseCcons[F[_]](implicit icc: IsCCons1[F, TraverseOrMk, MkTraverse]): MkTraverse[F] = + new MkTraverse[F] { + override def safeTraverse[G[_] : Applicative, A, B](fa: F[A])(f: A => Eval[G[B]]): Eval[G[F[B]]] = { + val gUnpacked: Eval[G[Either[icc.H[B], icc.T[B]]]] = + icc.unpack(fa) match { + case Left(hd) => apEval[G].map(icc.fh.unify.safeTraverse(hd)(f))(Left(_)) + case Right(tl) => apEval[G].map(icc.ft.safeTraverse(tl)(f))(Right(_)) + } + + apEval[G].map(gUnpacked)(icc.pack) + } + + 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.unify.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.unify.safeFoldLeft(hd, b)(f) + case Right(tl) => ft.safeFoldLeft(tl, b)(f) + } + } + } + +} + +private[derived] trait MkTraverse1 extends MkTraverse2 { + implicit def mkTraverseSplit[F[_]](implicit split: Split1[F, TraverseOrMk, TraverseOrMk]): MkTraverse[F] = + new MkTraverse[F] { + override def safeTraverse[G[_] : Applicative, A, B](fa: F[A])(f: A => Eval[G[B]]): Eval[G[F[B]]] = + split.fo.unify.safeTraverse(split.unpack(fa))(split.fi.unify.safeTraverse(_)(f)).map(_.map(split.pack)) + + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + import split._ + fo.unify.foldRight(unpack(fa), lb) { (fai, lbi) => fi.unify.foldRight(fai, lbi)(f) } + } + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = { + import split._ + fo.unify.safeFoldLeft(unpack(fa), b) { (lbi, fai) => fi.unify.safeFoldLeft(fai, lbi)(f) } + } + } +} + +private[derived] trait MkTraverse2 extends MkTraverseUtils { + implicit def mkTraverseGeneric[F[_]](implicit gen: Generic1[F, MkTraverse]): MkTraverse[F] = + new MkTraverse[F] { + override def safeTraverse[G[_] : Applicative, A, B](fa: F[A])(f: A => Eval[G[B]]): Eval[G[F[B]]] = + gen.fr.safeTraverse(gen.to(fa))(f).map(_.map(gen.from)) + + 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) + } +} + +private[derived] trait MkTraverseUtils { + + protected type TraverseOrMk[F[_]] = Traverse[F] OrElse MkTraverse[F] + + protected def apEval[G[_] : Applicative] = Applicative[Eval].compose[G] + + protected implicit class SafeTraverse[F[_]](val F: Traverse[F]) { + def safeTraverse[G[_] : Applicative, A, B](fa: F[A])(f: A => Eval[G[B]]): Eval[G[F[B]]] = F match { + case mk: MkTraverse[F] => mk.safeTraverse(fa)(f) + case _ => F.traverse[λ[t => Eval[G[t]]], A, B](fa)(f)(apEval[G]) + } + + def safeFoldLeft[A, B](fa: F[A], b: B)(f: (B, A) => Eval[B]): Eval[B] = F match { + case mff: MkTraverse[F] => mff.safeFoldLeft(fa, b)(f) + case _ => Now(F.foldLeft(fa, b) { (b, a) => f(b, a).value }) + } + } + +} diff --git a/core/src/test/scala/cats/derived/traverse.scala b/core/src/test/scala/cats/derived/traverse.scala new file mode 100644 index 00000000..5041fa59 --- /dev/null +++ b/core/src/test/scala/cats/derived/traverse.scala @@ -0,0 +1,163 @@ +package cats +package derived + +import cats.derived.TestDefns._ +import cats.implicits._ +import cats.laws.discipline.{FoldableTests, TraverseTests} +import org.scalacheck.Test +import org.scalatest.FreeSpec +import shapeless.test.illTyped + +class TraverseSuite extends FreeSpec { + + "traverse" - { + + "passes cats traverse tests" in { + Test.checkProperties( + Test.Parameters.default, + TraverseTests[IList](semi.traverse[IList]).traverse[Int, Double, String, Long, Option, Option].all + ) + } + + "derives an instance for" - { + // occasional map, as a special case of traverse[Id,_], is just fine and is easier to test + // the laws were checked in the previous test + + "for a Tree" in { + implicit val F = semi.traverse[Tree] + + val tree: Tree[String] = + Node( + Leaf("12"), + Node( + Leaf("3"), + Leaf("4") + ) + ) + + val expected: List[Tree[Char]] = List( + Node( + Leaf('1'), + Node( + Leaf('3'), + Leaf('4') + ) + ), + Node( + Leaf('2'), + Node( + Leaf('3'), + Leaf('4') + ) + ) + ) + + assert(tree.traverse(_.toCharArray.toList) == expected) + } + + "for a nested List[List[_]] (with alias)" in { + illTyped("derive.traverse[λ[t => List[List[t]]]]") + type LList[T] = List[List[T]] + val F = semi.traverse[LList] + + val l = List(List(1), List(2, 3), List(4, 5, 6), List(), List(7)) + val expected = List(List(2), List(3, 4), List(5, 6, 7), List(), List(8)) + + assert(F.map(l)(_ + 1) == expected) + } + + "for a pair on the left (with alias)" in { + illTyped("derive.traverse[(?, String)]") + + def F[R]: Traverse[(?, R)] = { + type Pair[L] = (L, R) + semi.traverse[Pair] + } + + val pair = (42, "shapeless") + assert(F[String].map(pair)(_ / 2) == (21, "shapeless")) + } + + "for a pair on the right" in { + def F[L]: Traverse[(L, ?)] = semi.traverse[(L, ?)] + + val pair = (42, "shapeless") + assert(F[Int].map(pair)(_.length) == (42, 9)) + } + + } + + "respects existing instances for a generic ADT " in { + implicit val F = semi.traverse[GenericAdt] + val adt: GenericAdt[Int] = GenericAdtCase(Some(2)) + assert(adt.map(_ + 1) == GenericAdtCase(Some(3))) + } + + "is stack safe" in { + implicit val F = semi.traverse[IList] + + val llarge = List.range(1, 10000) + val large = IList.fromSeq(llarge) + + val actual = large.traverse[Option, Int](i => Option(i + 1)).map(IList.toList) + val expected = Option(llarge.map(_ + 1)) + + assert(actual == expected) + } + + "auto derivation work for interleaved case class" in { + import cats.derived.auto.traverse._ + + val testInstance = TestTraverse(1, "ab", 2.0, List("cd"), "3") + + val expected = List( + TestTraverse(1, 'a', 2.0, List('c'), "3"), + TestTraverse(1, 'a', 2.0, List('d'), "3"), + TestTraverse(1, 'b', 2.0, List('c'), "3"), + TestTraverse(1, 'b', 2.0, List('d'), "3") + ) + val actual = testInstance.traverse(_.toCharArray.toList) + + assert(actual == expected) + } + + "folds" - { + + "pass cats fold tests" in { + Test.checkProperties( + Test.Parameters.default, + FoldableTests[IList](semi.traverse[IList]).foldable[Int, Double].all + ) + } + + "are implemented correctly" in { + implicit val F = semi.traverse[IList].asInstanceOf[MkTraverse[IList]] + + val iList = IList.fromSeq(List.range(1, 5)) + + // just basic sanity checks + assert(F.foldLeft(iList, "x")(_ + _) == "x1234") + assert(F.foldRight(iList, Now("x"))((i, b) => b.map(i + _)).value == "1234x") + assert(F.foldMap(iList)(_.toDouble) == 10) + } + + "are stack safe" in { + implicit val F = semi.traverse[IList] + + val n = 10000 + val llarge = IList.fromSeq(List.range(1, n)) + + val expected = n * (n - 1) / 2 + val evalActual = F.foldRight(llarge, Now(0))((buff, eval) => eval.map(_ + buff)) + + assert(evalActual.value == expected) + } + } + } + + implicit def eqTestClass[T: Eq]: Eq[IList[T]] = semi.eq + +} + +private case class TestTraverse[T](i: Int, t: T, d: Double, tt: List[T], s: String) + diff --git a/core/src/test/scala/cats/sequence/TraverseSuite.scala b/core/src/test/scala/cats/sequence/TraverseHListSuite.scala similarity index 95% rename from core/src/test/scala/cats/sequence/TraverseSuite.scala rename to core/src/test/scala/cats/sequence/TraverseHListSuite.scala index 015776c6..dd267994 100644 --- a/core/src/test/scala/cats/sequence/TraverseSuite.scala +++ b/core/src/test/scala/cats/sequence/TraverseHListSuite.scala @@ -11,7 +11,7 @@ import cats.derived._ import org.scalacheck.Prop.forAll -class TraverseSuite extends KittensSuite { +class TraverseHListSuite extends KittensSuite { def optToValidation[T](opt: Option[T]): Validated[String, T] = Validated.fromOption(opt, "Nothing Here")