diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 0e05fc45a5c..1eb96ac18f0 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -149,6 +149,22 @@ import scala.annotation.implicitNotFound override def unorderedSequence[G[_]: CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] = sequence(fga) + + /** + * a combination of map and scanLeft + * it applies a function to each element of a structure, + * passing an accumulating parameter from left to right, + * and returning a final value of this accumulator together with the new structure. + */ + def mapAccumL[A, B, C](fa: F[A], start: B)(step: (B, A) => (B, C)): (B, F[C]) = + traverse(fa)(a => State((b: B) => step(b, a))).run(start).value + + /** + * like [[mapAccumL]] but also combines monadic effects of `G` + * stack-safety relies on a stack safety of G + */ + def mapAccumF[G[_]: Monad, A, B, C](fa: F[A], start: B)(step: (B, A) => G[(B, C)]): G[(B, F[C])] = + traverse(fa)(a => StateT((b: B) => step(b, a))).run(start) } object Traverse { @@ -166,48 +182,38 @@ object Traverse { object ops { implicit def toAllTraverseOps[F[_], A](target: F[A])(implicit tc: Traverse[F]): AllOps[F, A] { type TypeClassType = Traverse[F] - } = - new AllOps[F, A] { - type TypeClassType = Traverse[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new AllOps[F, A] { + type TypeClassType = Traverse[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } trait Ops[F[_], A] extends Serializable { type TypeClassType <: Traverse[F] def self: F[A] val typeClassInstance: TypeClassType - def traverse[G[_], B](f: A => G[B])(implicit ev$1: Applicative[G]): G[F[B]] = - typeClassInstance.traverse[G, A, B](self)(f) - def traverseTap[G[_], B](f: A => G[B])(implicit ev$1: Applicative[G]): G[F[A]] = - typeClassInstance.traverseTap[G, A, B](self)(f) - def flatTraverse[G[_], B](f: A => G[F[B]])(implicit G: Applicative[G], F: FlatMap[F]): G[F[B]] = - typeClassInstance.flatTraverse[G, A, B](self)(f)(G, F) - def sequence[G[_], B](implicit ev$1: A <:< G[B], ev$2: Applicative[G]): G[F[B]] = - typeClassInstance.sequence[G, B](self.asInstanceOf[F[G[B]]]) - def flatSequence[G[_], B](implicit ev$1: A <:< G[F[B]], G: Applicative[G], F: FlatMap[F]): G[F[B]] = - typeClassInstance.flatSequence[G, B](self.asInstanceOf[F[G[F[B]]]])(G, F) + def traverse[G[_], B](f: A => G[B])(implicit ev$1: Applicative[G]): G[F[B]] = typeClassInstance.traverse[G, A, B](self)(f) + def traverseTap[G[_], B](f: A => G[B])(implicit ev$1: Applicative[G]): G[F[A]] = typeClassInstance.traverseTap[G, A, B](self)(f) + def flatTraverse[G[_], B](f: A => G[F[B]])(implicit G: Applicative[G], F: FlatMap[F]): G[F[B]] = typeClassInstance.flatTraverse[G, A, B](self)(f)(G, F) + def sequence[G[_], B](implicit ev$1: A <:< G[B], ev$2: Applicative[G]): G[F[B]] = typeClassInstance.sequence[G, B](self.asInstanceOf[F[G[B]]]) + def flatSequence[G[_], B](implicit ev$1: A <:< G[F[B]], G: Applicative[G], F: FlatMap[F]): G[F[B]] = typeClassInstance.flatSequence[G, B](self.asInstanceOf[F[G[F[B]]]])(G, F) def mapWithIndex[B](f: (A, Int) => B): F[B] = typeClassInstance.mapWithIndex[A, B](self)(f) - def traverseWithIndexM[G[_], B](f: (A, Int) => G[B])(implicit G: Monad[G]): G[F[B]] = - typeClassInstance.traverseWithIndexM[G, A, B](self)(f)(G) + def traverseWithIndexM[G[_], B](f: (A, Int) => G[B])(implicit G: Monad[G]): G[F[B]] = typeClassInstance.traverseWithIndexM[G, A, B](self)(f)(G) def zipWithIndex: F[(A, Int)] = typeClassInstance.zipWithIndex[A](self) + def mapAccumL[B, C](start: B)(step: (B, A) => (B, C)): (B, F[C]) = typeClassInstance.mapAccumL[A, B, C](self, start)(step) + def mapAccumF[G[_], B, C](start: B)(step: (B, A) => G[(B, C)])(implicit ev$1: Monad[G]): G[(B, F[C])] = typeClassInstance.mapAccumF[G, A, B, C](self, start)(step) } - trait AllOps[F[_], A] - extends Ops[F, A] - with Functor.AllOps[F, A] - with Foldable.AllOps[F, A] - with UnorderedTraverse.AllOps[F, A] { + trait AllOps[F[_], A] extends Ops[F, A] with Functor.AllOps[F, A] with Foldable.AllOps[F, A] with UnorderedTraverse.AllOps[F, A] { type TypeClassType <: Traverse[F] } trait ToTraverseOps extends Serializable { implicit def toTraverseOps[F[_], A](target: F[A])(implicit tc: Traverse[F]): Ops[F, A] { type TypeClassType = Traverse[F] - } = - new Ops[F, A] { - type TypeClassType = Traverse[F] - val self: F[A] = target - val typeClassInstance: TypeClassType = tc - } + } = new Ops[F, A] { + type TypeClassType = Traverse[F] + val self: F[A] = target + val typeClassInstance: TypeClassType = tc + } } @deprecated("Use cats.syntax object imports", "2.2.0") object nonInheritedOps extends ToTraverseOps @@ -216,4 +222,10 @@ object Traverse { /* END OF SIMULACRUM-MANAGED CODE */ /* ======================================================================== */ + + + + + + } diff --git a/tests/src/test/scala/cats/tests/TraverseSuite.scala b/tests/src/test/scala/cats/tests/TraverseSuite.scala index 1138952be14..9e617ef3ca5 100644 --- a/tests/src/test/scala/cats/tests/TraverseSuite.scala +++ b/tests/src/test/scala/cats/tests/TraverseSuite.scala @@ -32,6 +32,18 @@ abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arb } } + test(s"Traverse[$name].mapAccumL") { + forAll { (fa: F[Int], start: Long, fn: (Long, Int) => (Long, Int)) => + val (act, fb) = fa.mapAccumL(start)(fn) + val (exp, xs) = fa.toList.foldLeft((start, List.empty[Int])) { case ((prev, acc), a) => + val (next, b) = fn(prev, a) + (next, b :: acc) + } + assert(act === exp) + assert(fb.toList === xs.reverse) + } + } + } object TraverseSuite {