From 2b99ad15801315049fde39ca6131bf7eb4b8f701 Mon Sep 17 00:00:00 2001 From: Oleg Nizhnik Date: Tue, 16 Feb 2021 10:50:15 +0300 Subject: [PATCH] Added mapAccumL and mapAccumM operations --- core/src/main/scala/cats/Traverse.scala | 42 +++++++++++++------ .../test/scala/cats/tests/TraverseSuite.scala | 12 ++++++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 0e05fc45a5..0c93278312 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 mapAccumM[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,12 +182,11 @@ 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] @@ -191,6 +206,10 @@ object Traverse { 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 mapAccumM[G[_], B, C](start: B)(step: (B, A) => G[(B, C)])(implicit ev$1: Monad[G]): G[(B, F[C])] = + typeClassInstance.mapAccumM[G, A, B, C](self, start)(step) } trait AllOps[F[_], A] extends Ops[F, A] @@ -202,12 +221,11 @@ object Traverse { 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 diff --git a/tests/src/test/scala/cats/tests/TraverseSuite.scala b/tests/src/test/scala/cats/tests/TraverseSuite.scala index 1138952be1..9e617ef3ca 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 {