From b233e773197387e13890d2150c569571b4f0c89f Mon Sep 17 00:00:00 2001 From: nikololiahim Date: Tue, 21 Jun 2022 09:46:32 +0300 Subject: [PATCH 1/4] added `zipWithLongIndex`, `mapWithLongIndex` and `traverseWithLongIndexM` --- core/src/main/scala/cats/Traverse.scala | 24 +++++++++++++++++++ .../test/scala/cats/tests/TraverseSuite.scala | 20 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index f57300248e..b15de2dad6 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -168,6 +168,24 @@ trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[ def zipWithIndex[A](fa: F[A]): F[(A, Int)] = mapWithIndex(fa)((a, i) => (a, i)) + /** + * Same as [[traverseWithIndexM]] but the index type is [[Long]] instead of [[Int]]. + */ + def traverseWithLongIndexM[G[_], A, B](fa: F[A])(f: (A, Long) => G[B])(implicit G: Monad[G]): G[F[B]] = + traverse(fa)(a => StateT((s: Long) => G.map(f(a, s))(b => (s + 1, b)))).runA(0L) + + /** + * Same as [[mapWithIndex]] but the index type is [[Long]] instead of [[Int]]. + */ + def mapWithLongIndex[A, B](fa: F[A])(f: (A, Long) => B): F[B] = + traverseWithLongIndexM[cats.Id, A, B](fa)((a, long) => f(a, long)) + + /** + * Same as [[zipWithIndex]] but the index type is [[Long]] instead of [[Int]]. + */ + def zipWithLongIndex[A](fa: F[A]): F[(A, Long)] = + traverseWithLongIndexM[cats.Id, A, (A, Long)](fa)((a, long) => (a, long)) + override def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: (A) => G[B]): G[F[B]] = traverse(sa)(f) @@ -215,6 +233,12 @@ object Traverse { typeClassInstance.traverseWithIndexM[G, A, B](self)(f)(G) def zipWithIndex: F[(A, Int)] = typeClassInstance.zipWithIndex[A](self) + def zipWithLongIndex: F[(A, Long)] = + typeClassInstance.zipWithLongIndex[A](self) + def traverseWithLongIndexM[G[_], B](f: (A, Long) => G[B])(implicit G: Monad[G]): G[F[B]] = + typeClassInstance.traverseWithLongIndexM[G, A, B](self)(f) + def mapWithLongIndex[B](f: (A, Long) => B): F[B] = + typeClassInstance.mapWithLongIndex[A, B](self)(f) } trait AllOps[F[_], A] extends Ops[F, A] diff --git a/tests/shared/src/test/scala/cats/tests/TraverseSuite.scala b/tests/shared/src/test/scala/cats/tests/TraverseSuite.scala index 4b893758e1..07afa07a3d 100644 --- a/tests/shared/src/test/scala/cats/tests/TraverseSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/TraverseSuite.scala @@ -36,6 +36,12 @@ abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arb } } + test(s"Traverse[$name].zipWithLongIndex") { + forAll { (fa: F[Int]) => + assert(fa.zipWithLongIndex.toList === (fa.toList.zipWithLongIndex)) + } + } + test(s"Traverse[$name].mapAccumulate") { forAll { (init: Int, fa: F[Int], fn: ((Int, Int)) => (Int, Int)) => val lhs = fa.mapAccumulate(init)((s, a) => fn((s, a))) @@ -55,6 +61,12 @@ abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arb } } + test(s"Traverse[$name].mapWithLongIndex") { + forAll { (fa: F[Int], fn: ((Int, Long)) => Int) => + assert(fa.mapWithLongIndex((a, i) => fn((a, i))).toList === (fa.toList.zipWithLongIndex.map(fn))) + } + } + test(s"Traverse[$name].traverseWithIndexM") { forAll { (fa: F[Int], fn: ((Int, Int)) => (Int, Int)) => val left = fa.traverseWithIndexM((a, i) => fn((a, i))).fmap(_.toList) @@ -63,6 +75,14 @@ abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arb } } + test(s"Traverse[$name].traverseWithLongIndexM") { + forAll { (fa: F[Int], fn: ((Int, Long)) => (Int, Long)) => + val left = fa.traverseWithLongIndexM((a, i) => fn((a, i))).fmap(_.toList) + val (xs, values) = fa.toList.zipWithLongIndex.map(fn).unzip + assert(left === ((xs.combineAll, values))) + } + } + test(s"Traverse[$name].traverse matches traverse_ with Option") { forAll { (fa: F[Int], fn: Int => Option[Int]) => assert(Applicative[Option].void(fa.traverse(fn)) == fa.traverse_(fn)) From c1f9ed6992eb65ce154e29991e991b7686ec6d73 Mon Sep 17 00:00:00 2001 From: nikololiahim Date: Tue, 21 Jun 2022 13:56:11 +0300 Subject: [PATCH 2/4] added laws, static methods --- .../scala/cats/instances/StaticMethods.scala | 10 ++++++++++ .../main/scala/cats/laws/TraverseLaws.scala | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/core/src/main/scala/cats/instances/StaticMethods.scala b/core/src/main/scala/cats/instances/StaticMethods.scala index 24cbfad4b6..8df598bddd 100644 --- a/core/src/main/scala/cats/instances/StaticMethods.scala +++ b/core/src/main/scala/cats/instances/StaticMethods.scala @@ -60,4 +60,14 @@ private[cats] object StaticMethods { } } + def mapWithLongIndexFromStrictFunctor[F[_], A, B](fa: F[A], f: (A, Long) => B)(implicit ev: Functor[F]): F[B] = { + var idx: Long = 0L + + ev.map(fa) { a => + val b = f(a, idx) + idx += 1 + b + } + } + } diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index cfcb419ff2..dc78bca393 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -129,6 +129,24 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] with Unorde val rhs = F.map(F.mapWithIndex(fa)((a, i) => (a, i)))(f) lhs <-> rhs } + + def mapWithLongIndexRef[A, B](fa: F[A], f: (A, Long) => B): IsEq[F[B]] = { + val lhs = F.mapWithLongIndex(fa)(f) + val rhs = F.traverse(fa)(a => State((s: Long) => (s + 1, f(a, s)))).runA(0).value + lhs <-> rhs + } + + def traverseWithLongIndexMRef[G[_], A, B](fa: F[A], f: (A, Long) => G[B])(implicit G: Monad[G]): IsEq[G[F[B]]] = { + val lhs = F.traverseWithLongIndexM(fa)(f) + val rhs = F.traverse(fa)(a => StateT((s: Long) => G.map(f(a, s))(b => (s + 1, b)))).runA(0) + lhs <-> rhs + } + + def zipWithLongIndexRef[A, B](fa: F[A], f: ((A, Long)) => B): IsEq[F[B]] = { + val lhs = F.map(F.zipWithLongIndex(fa))(f) + val rhs = F.map(F.mapWithLongIndex(fa)((a, i) => (a, i)))(f) + lhs <-> rhs + } } object TraverseLaws { From 6fe035f533b7ff1d46eb297c4b2d6b9954dcdd53 Mon Sep 17 00:00:00 2001 From: nikololiahim Date: Tue, 21 Jun 2022 21:29:30 +0300 Subject: [PATCH 3/4] overrides for mapWithLongIndex --- core/src/main/scala/cats/data/Chain.scala | 3 +++ core/src/main/scala/cats/data/NonEmptyChain.scala | 3 +++ core/src/main/scala/cats/data/NonEmptyList.scala | 3 +++ core/src/main/scala/cats/data/NonEmptyVector.scala | 3 +++ core/src/main/scala/cats/instances/list.scala | 3 +++ core/src/main/scala/cats/instances/queue.scala | 3 +++ core/src/main/scala/cats/instances/vector.scala | 3 +++ laws/src/main/scala/cats/laws/TraverseLaws.scala | 4 ++-- 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 0cbf2839b9..112c890d7b 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -1256,6 +1256,9 @@ sealed abstract private[data] class ChainInstances extends ChainInstances1 { override def mapWithIndex[A, B](fa: Chain[A])(f: (A, Int) => B): Chain[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) + override def mapWithLongIndex[A, B](fa: Chain[A])(f: (A, Long) => B): Chain[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def zipWithIndex[A](fa: Chain[A]): Chain[(A, Int)] = fa.zipWithIndex diff --git a/core/src/main/scala/cats/data/NonEmptyChain.scala b/core/src/main/scala/cats/data/NonEmptyChain.scala index d2f1968fe1..7c8e70c90e 100644 --- a/core/src/main/scala/cats/data/NonEmptyChain.scala +++ b/core/src/main/scala/cats/data/NonEmptyChain.scala @@ -639,6 +639,9 @@ sealed abstract private[data] class NonEmptyChainInstances extends NonEmptyChain override def mapWithIndex[A, B](fa: NonEmptyChain[A])(f: (A, Int) => B): NonEmptyChain[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) + override def mapWithLongIndex[A, B](fa: NonEmptyChain[A])(f: (A, Long) => B): NonEmptyChain[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def zipWithIndex[A](fa: NonEmptyChain[A]): NonEmptyChain[(A, Int)] = fa.zipWithIndex diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index d72827b63f..aff19f3a8c 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -794,6 +794,9 @@ sealed abstract private[data] class NonEmptyListInstances extends NonEmptyListIn override def mapWithIndex[A, B](fa: NonEmptyList[A])(f: (A, Int) => B): NonEmptyList[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) + override def mapWithLongIndex[A, B](fa: NonEmptyList[A])(f: (A, Long) => B): NonEmptyList[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def zipWithIndex[A](fa: NonEmptyList[A]): NonEmptyList[(A, Int)] = fa.zipWithIndex diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 22117f86da..18e7d55963 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -442,6 +442,9 @@ sealed abstract private[data] class NonEmptyVectorInstances { ): (S, NonEmptyVector[B]) = StaticMethods.mapAccumulateFromStrictFunctor(init, fa, f)(this) + override def mapWithLongIndex[A, B](fa: NonEmptyVector[A])(f: (A, Long) => B): NonEmptyVector[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def mapWithIndex[A, B](fa: NonEmptyVector[A])(f: (A, Int) => B): NonEmptyVector[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 979438171a..e65cb5b193 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -188,6 +188,9 @@ trait ListInstances extends cats.kernel.instances.ListInstances { override def mapAccumulate[S, A, B](init: S, fa: List[A])(f: (S, A) => (S, B)): (S, List[B]) = StaticMethods.mapAccumulateFromStrictFunctor(init, fa, f)(this) + override def mapWithLongIndex[A, B](fa: List[A])(f: (A, Long) => B): List[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def mapWithIndex[A, B](fa: List[A])(f: (A, Int) => B): List[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) diff --git a/core/src/main/scala/cats/instances/queue.scala b/core/src/main/scala/cats/instances/queue.scala index ed16624492..a54c920992 100644 --- a/core/src/main/scala/cats/instances/queue.scala +++ b/core/src/main/scala/cats/instances/queue.scala @@ -132,6 +132,9 @@ trait QueueInstances extends cats.kernel.instances.QueueInstances { override def mapAccumulate[S, A, B](init: S, fa: Queue[A])(f: (S, A) => (S, B)): (S, Queue[B]) = StaticMethods.mapAccumulateFromStrictFunctor(init, fa, f)(this) + override def mapWithLongIndex[A, B](fa: Queue[A])(f: (A, Long) => B): Queue[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def mapWithIndex[A, B](fa: Queue[A])(f: (A, Int) => B): Queue[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 5632b6bad3..31a003c6c0 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -174,6 +174,9 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { override def mapWithIndex[A, B](fa: Vector[A])(f: (A, Int) => B): Vector[B] = StaticMethods.mapWithIndexFromStrictFunctor(fa, f)(this) + override def mapWithLongIndex[A, B](fa: Vector[A])(f: (A, Long) => B): Vector[B] = + StaticMethods.mapWithLongIndexFromStrictFunctor(fa, f)(this) + override def zipWithIndex[A](fa: Vector[A]): Vector[(A, Int)] = fa.zipWithIndex diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index dc78bca393..ce1c23cda6 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -132,13 +132,13 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] with Unorde def mapWithLongIndexRef[A, B](fa: F[A], f: (A, Long) => B): IsEq[F[B]] = { val lhs = F.mapWithLongIndex(fa)(f) - val rhs = F.traverse(fa)(a => State((s: Long) => (s + 1, f(a, s)))).runA(0).value + val rhs = F.traverse(fa)(a => State((s: Long) => (s + 1, f(a, s)))).runA(0L).value lhs <-> rhs } def traverseWithLongIndexMRef[G[_], A, B](fa: F[A], f: (A, Long) => G[B])(implicit G: Monad[G]): IsEq[G[F[B]]] = { val lhs = F.traverseWithLongIndexM(fa)(f) - val rhs = F.traverse(fa)(a => StateT((s: Long) => G.map(f(a, s))(b => (s + 1, b)))).runA(0) + val rhs = F.traverse(fa)(a => StateT((s: Long) => G.map(f(a, s))(b => (s + 1, b)))).runA(0L) lhs <-> rhs } From 609a4f1958db09ca1781042d95a09dbc78d9e08b Mon Sep 17 00:00:00 2001 From: nikololiahim Date: Tue, 21 Jun 2022 22:02:15 +0300 Subject: [PATCH 4/4] zipWithLongIndex delegates to mapWithLongIndex --- core/src/main/scala/cats/Traverse.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index b15de2dad6..8b753660e1 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -184,7 +184,7 @@ trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[ * Same as [[zipWithIndex]] but the index type is [[Long]] instead of [[Int]]. */ def zipWithLongIndex[A](fa: F[A]): F[(A, Long)] = - traverseWithLongIndexM[cats.Id, A, (A, Long)](fa)((a, long) => (a, long)) + mapWithLongIndex(fa)((a, long) => (a, long)) override def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: (A) => G[B]): G[F[B]] = traverse(sa)(f)