From 31c2c9487420c1ee2ac5fa68fca055ac317b4517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 27 Jan 2020 18:48:25 -0500 Subject: [PATCH 1/6] Adding implementation --- .../main/scala-2.13+/cats/instances/all.scala | 1 + .../scala-2.13+/cats/instances/arraySeq.scala | 146 ++++++++++++++++++ .../scala-2.13+/cats/instances/package.scala | 1 + .../cats/kernel/instances/AllInstances.scala | 3 +- .../kernel/instances/ArraySeqInstances.scala | 93 +++++++++++ .../kernel/instances/arraySeq/package.scala | 4 + 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala-2.13+/cats/instances/arraySeq.scala create mode 100644 kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala create mode 100644 kernel/src/main/scala-2.13+/cats/kernel/instances/arraySeq/package.scala diff --git a/core/src/main/scala-2.13+/cats/instances/all.scala b/core/src/main/scala-2.13+/cats/instances/all.scala index b6c7744315..9c473746dc 100644 --- a/core/src/main/scala-2.13+/cats/instances/all.scala +++ b/core/src/main/scala-2.13+/cats/instances/all.scala @@ -13,6 +13,7 @@ abstract class AllInstancesBinCompat trait AllInstances extends AnyValInstances + with ArraySeqInstances with BigIntInstances with BigDecimalInstances with BitSetInstances diff --git a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala new file mode 100644 index 0000000000..faa0a1b5f6 --- /dev/null +++ b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala @@ -0,0 +1,146 @@ +package cats +package instances + +import scala.collection.immutable.ArraySeq + +trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { + + implicit val catsStdInstancesForArraySeq: Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] = + new Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] { + def empty[A]: ArraySeq[A] = + ArraySeq.untagged.empty + + def combineK[A](xs: ArraySeq[A], ys: ArraySeq[A]): ArraySeq[A] = + xs.concat(ys) + + override def algebra[A]: Monoid[ArraySeq[A]] = + new cats.kernel.instances.ArraySeqMonoid[A] + + def pure[A](a: A): ArraySeq[A] = + ArraySeq.untagged.fill(n = 1)(elem = a) + + override def map[A, B](fa: ArraySeq[A])(f: A => B): ArraySeq[B] = + fa.map(f) + + def flatMap[A, B](fa: ArraySeq[A])(f: A => ArraySeq[B]): ArraySeq[B] = + fa.flatMap(f) + + override def map2[A, B, Z](fa: ArraySeq[A], fb: ArraySeq[B])(f: (A, B) => Z): ArraySeq[Z] = + if (fb.isEmpty) ArraySeq.empty // do O(1) work if fb is empty + else fa.flatMap(a => fb.map(b => f(a, b))) // already O(1) if fa is empty + + override def map2Eval[A, B, Z](fa: ArraySeq[A], fb: Eval[ArraySeq[B]])(f: (A, B) => Z): Eval[ArraySeq[Z]] = + if (fa.isEmpty) Eval.now(ArraySeq.empty) // no need to evaluate fb + else fb.map(fb => map2(fa, fb)(f)) + + def foldLeft[A, B](fa: ArraySeq[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: ArraySeq[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(i: Int): Eval[B] = + if (i < fa.length) f(fa(i), Eval.defer(loop(i + 1))) else lb + + Eval.defer(loop(0)) + } + + override def foldMap[A, B](fa: ArraySeq[A])(f: A => B)(implicit B: Monoid[B]): B = + B.combineAll(fa.iterator.map(f)) + + def traverse[G[_], A, B](fa: ArraySeq[A])(f: A => G[B])(implicit G: Applicative[G]): G[ArraySeq[B]] = + foldRight[A, G[ArraySeq[B]]](fa, Always(G.pure(ArraySeq.untagged.empty))) { + case (a, lgvb) => + G.map2Eval(f(a), lgvb)(_ +: _) + }.value + + override def mapWithIndex[A, B](fa: ArraySeq[A])(f: (A, Int) => B): ArraySeq[B] = + ArraySeq.untagged.tabulate(n = fa.length) { i => + f(fa(i), i) + } + + override def zipWithIndex[A](fa: ArraySeq[A]): ArraySeq[(A, Int)] = + fa.zipWithIndex + + def tailRecM[A, B](a: A)(fn: A => ArraySeq[Either[A, B]]): ArraySeq[B] = + ??? + + override def exists[A](fa: ArraySeq[A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def forall[A](fa: ArraySeq[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def get[A](fa: ArraySeq[A])(idx: Long): Option[A] = + if (idx < fa.length && idx.isValidInt) Some(fa(idx.toInt)) else None + + override def isEmpty[A](fa: ArraySeq[A]): Boolean = + fa.isEmpty + + override def foldM[G[_], A, B](fa: ArraySeq[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = + G.tailRecM((z, 0)) { + case (b, i) => + if (i < fa.length) G.map(f(b, fa(i)))(b => Left((b, i + 1))) + else G.pure(Right(b)) + } + + override def fold[A](fa: ArraySeq[A])(implicit A: Monoid[A]): A = + A.combineAll(fa) + + override def toList[A](fa: ArraySeq[A]): List[A] = + fa.toList + + override def toIterable[A](fa: ArraySeq[A]): Iterable[A] = + fa + + override def reduceLeftOption[A](fa: ArraySeq[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: ArraySeq[A])(f: A => Boolean): Option[A] = + fa.find(f) + + override def collectFirst[A, B](fa: ArraySeq[A])(pf: PartialFunction[A, B]): Option[B] = + fa.collectFirst(pf) + + override def collectFirstSome[A, B](fa: ArraySeq[A])(f: A => Option[B]): Option[B] = + fa.collectFirst(Function.unlift(f)) + + def functor: Functor[ArraySeq] = this + } + + implicit def catsStdShowForArraySeq[A: Show]: Show[ArraySeq[A]] = + Show.fromToString + + implicit val catsStdTraverseFilterForArraySeq: TraverseFilter[ArraySeq] = new TraverseFilter[ArraySeq] { + val traverse: Traverse[ArraySeq] = catsStdInstancesForArraySeq + + override def mapFilter[A, B](fa: ArraySeq[A])(f: (A) => Option[B]): ArraySeq[B] = + fa.collect(Function.unlift(f)) + + override def filter[A](fa: ArraySeq[A])(f: (A) => Boolean): ArraySeq[A] = + fa.filter(f) + + override def filterNot[A](fa: ArraySeq[A])(f: (A) => Boolean): ArraySeq[A] = + fa.filterNot(f) + + override def collect[A, B](fa: ArraySeq[A])(f: PartialFunction[A, B]): ArraySeq[B] = + fa.collect(f) + + override def flattenOption[A](fa: ArraySeq[Option[A]]): ArraySeq[A] = + fa.flatten + + def traverseFilter[G[_], A, B]( + fa: ArraySeq[A] + )(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[ArraySeq[B]] = + fa.foldRight(Eval.now(G.pure(ArraySeq.untagged.empty[B]))) { + case (x, xse) => + G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) + } + .value + + override def filterA[G[_], A](fa: ArraySeq[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[ArraySeq[A]] = + fa.foldRight(Eval.now(G.pure(ArraySeq.untagged.empty[A]))) { + case (x, xse) => + G.map2Eval(f(x), xse)((b, vec) => if (b) x +: vec else vec) + } + .value + } +} diff --git a/core/src/main/scala-2.13+/cats/instances/package.scala b/core/src/main/scala-2.13+/cats/instances/package.scala index 4dfa9bbadc..ed42de4c2a 100644 --- a/core/src/main/scala-2.13+/cats/instances/package.scala +++ b/core/src/main/scala-2.13+/cats/instances/package.scala @@ -2,6 +2,7 @@ package cats package object instances { object all extends AllInstancesBinCompat + object arraySeq extends ArraySeqInstances object bigInt extends BigIntInstances object bigDecimal extends BigDecimalInstances object bitSet extends BitSetInstances diff --git a/kernel/src/main/scala-2.13+/cats/kernel/instances/AllInstances.scala b/kernel/src/main/scala-2.13+/cats/kernel/instances/AllInstances.scala index 90407bb0f6..6c1b3436ca 100644 --- a/kernel/src/main/scala-2.13+/cats/kernel/instances/AllInstances.scala +++ b/kernel/src/main/scala-2.13+/cats/kernel/instances/AllInstances.scala @@ -2,7 +2,8 @@ package cats.kernel package instances trait AllInstances - extends BigDecimalInstances + extends ArraySeqInstances + with BigDecimalInstances with BigIntInstances with BitSetInstances with BooleanInstances diff --git a/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala b/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala new file mode 100644 index 0000000000..70914e6748 --- /dev/null +++ b/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala @@ -0,0 +1,93 @@ +package cats.kernel +package instances + +import scala.annotation.tailrec +import scala.collection.immutable.ArraySeq +import scala.reflect.ClassTag +import compat.scalaVersionSpecific._ + +@suppressUnusedImportWarningForScalaVersionSpecific +trait ArraySeqInstances extends ArraySeqInstances1 { + implicit def catsKernelStdOrderForArraySeq[A: Order]: Order[ArraySeq[A]] = + new ArraySeqOrder[A] + + implicit def catsKernelStdMonoidForArraySeq[A: ClassTag]: Monoid[ArraySeq[A]] = + new ArraySeqMonoid[A] +} + +private[cats] trait ArraySeqInstances1 extends ArraySeqInstances2 { + implicit def catsKernelStdPartialOrderForArraySeq[A: PartialOrder]: PartialOrder[ArraySeq[A]] = + new ArraySeqPartialOrder[A] + + implicit def catsKernelStdHashForArraySeq[A: Hash]: Hash[ArraySeq[A]] = + new ArraySeqHash[A] +} + +private[cats] trait ArraySeqInstances2 { + implicit def catsKernelStdEqForArraySeq[A: Eq]: Eq[ArraySeq[A]] = + new ArraySeqEq[A] +} + +final private[cats] class ArraySeqOrder[A](implicit ev: Order[A]) extends Order[ArraySeq[A]] { + def compare(xs: ArraySeq[A], ys: ArraySeq[A]): Int = { + @tailrec def loop(i: Int): Int = + (i < xs.length, i < ys.length) match { + case (true, true) => + val n = ev.compare(xs(i), ys(i)) + if (n != 0) n else loop(i + 1) + case (true, false) => 1 + case (false, true) => -1 + case (false, false) => 0 + } + + if (xs eq ys) 0 else loop(i = 0) + } +} + +private[cats] class ArraySeqPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[ArraySeq[A]] { + def partialCompare(xs: ArraySeq[A], ys: ArraySeq[A]): Double = { + @tailrec def loop(i: Int): Double = + (i < xs.length, i < ys.length) match { + case (true, true) => + val n = ev.partialCompare(xs(i), ys(i)) + if (n != 0) n else loop(i + 1) + case (true, false) => 1 + case (false, true) => -1 + case (false, false) => 0 + } + + if (xs eq ys) 0.0 else loop(i = 0) + } +} + +private[cats] class ArraySeqHash[A](implicit ev: Hash[A]) extends ArraySeqEq[A]()(ev) with Hash[ArraySeq[A]] { + def hash(xs: ArraySeq[A]): Int = StaticMethods.orderedHash(xs) +} + +private[cats] class ArraySeqEq[A](implicit ev: Eq[A]) extends Eq[ArraySeq[A]] { + def eqv(xs: ArraySeq[A], ys: ArraySeq[A]): Boolean = { + @tailrec def loop(i: Int): Boolean = + (i < xs.length, i < ys.length) match { + case (true, true) => if (ev.eqv(xs(i), ys(i))) loop(i + 1) else false + case (true, false) => false + case (false, true) => false + case (false, false) => true + } + + (xs eq ys) || loop(i = 0) + } +} + +final private[cats] class ArraySeqMonoid[A] extends Monoid[ArraySeq[A]] { + def empty: ArraySeq[A] = + ArraySeq.untagged.empty + + def combine(xs: ArraySeq[A], ys: ArraySeq[A]): ArraySeq[A] = + xs.concat(ys) + + override def combineN(x: ArraySeq[A], n: Int): ArraySeq[A] = + StaticMethods.combineNIterable(ArraySeq.untagged.newBuilder[A], x, n) + + override def combineAll(xs: IterableOnce[ArraySeq[A]]): ArraySeq[A] = + StaticMethods.combineAllIterable(ArraySeq.untagged.newBuilder[A], xs) +} diff --git a/kernel/src/main/scala-2.13+/cats/kernel/instances/arraySeq/package.scala b/kernel/src/main/scala-2.13+/cats/kernel/instances/arraySeq/package.scala new file mode 100644 index 0000000000..0babdc5084 --- /dev/null +++ b/kernel/src/main/scala-2.13+/cats/kernel/instances/arraySeq/package.scala @@ -0,0 +1,4 @@ +package cats.kernel +package instances + +package object arraySeq extends ArraySeqInstances // scalastyle:ignore package.object.name From 5554a6fe09df6df4ed453172d414607f90efeb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 28 Jan 2020 10:28:04 -0500 Subject: [PATCH 2/6] Fixing binary incompability issue --- .../scala-2.13+/cats/instances/arraySeq.scala | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala index faa0a1b5f6..9219aff56a 100644 --- a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala +++ b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala @@ -4,8 +4,20 @@ package instances import scala.collection.immutable.ArraySeq trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { + implicit def catsStdInstancesForArraySeq: Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] = + ArraySeqInstances.stdInstances - implicit val catsStdInstancesForArraySeq: Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] = + implicit def catsStdTraverseFilterForArraySeq: TraverseFilter[ArraySeq] = + ArraySeqInstances.stdTraverseFilterInstance + + implicit def catsStdShowForArraySeq[A](implicit ev: Show[A]): Show[ArraySeq[A]] = + Show.show { arraySeq => + arraySeq.iterator.map(ev.show).mkString("ArraySeq(", ", ", ")") + } +} + +object ArraySeqInstances { + final private val stdInstances = new Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] { def empty[A]: ArraySeq[A] = ArraySeq.untagged.empty @@ -102,45 +114,41 @@ trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { override def collectFirstSome[A, B](fa: ArraySeq[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f)) - - def functor: Functor[ArraySeq] = this } - implicit def catsStdShowForArraySeq[A: Show]: Show[ArraySeq[A]] = - Show.fromToString + final private val stdTraverseFilterInstance = + new TraverseFilter[ArraySeq] { + val traverse: Traverse[ArraySeq] = stdInstances - implicit val catsStdTraverseFilterForArraySeq: TraverseFilter[ArraySeq] = new TraverseFilter[ArraySeq] { - val traverse: Traverse[ArraySeq] = catsStdInstancesForArraySeq + override def mapFilter[A, B](fa: ArraySeq[A])(f: (A) => Option[B]): ArraySeq[B] = + fa.collect(Function.unlift(f)) - override def mapFilter[A, B](fa: ArraySeq[A])(f: (A) => Option[B]): ArraySeq[B] = - fa.collect(Function.unlift(f)) + override def filter[A](fa: ArraySeq[A])(f: (A) => Boolean): ArraySeq[A] = + fa.filter(f) - override def filter[A](fa: ArraySeq[A])(f: (A) => Boolean): ArraySeq[A] = - fa.filter(f) + override def filterNot[A](fa: ArraySeq[A])(f: (A) => Boolean): ArraySeq[A] = + fa.filterNot(f) - override def filterNot[A](fa: ArraySeq[A])(f: (A) => Boolean): ArraySeq[A] = - fa.filterNot(f) + override def collect[A, B](fa: ArraySeq[A])(f: PartialFunction[A, B]): ArraySeq[B] = + fa.collect(f) - override def collect[A, B](fa: ArraySeq[A])(f: PartialFunction[A, B]): ArraySeq[B] = - fa.collect(f) + override def flattenOption[A](fa: ArraySeq[Option[A]]): ArraySeq[A] = + fa.flatten - override def flattenOption[A](fa: ArraySeq[Option[A]]): ArraySeq[A] = - fa.flatten + def traverseFilter[G[_], A, B]( + fa: ArraySeq[A] + )(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[ArraySeq[B]] = + fa.foldRight(Eval.now(G.pure(ArraySeq.untagged.empty[B]))) { + case (x, xse) => + G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) + } + .value - def traverseFilter[G[_], A, B]( - fa: ArraySeq[A] - )(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[ArraySeq[B]] = - fa.foldRight(Eval.now(G.pure(ArraySeq.untagged.empty[B]))) { - case (x, xse) => - G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) - } - .value - - override def filterA[G[_], A](fa: ArraySeq[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[ArraySeq[A]] = - fa.foldRight(Eval.now(G.pure(ArraySeq.untagged.empty[A]))) { - case (x, xse) => - G.map2Eval(f(x), xse)((b, vec) => if (b) x +: vec else vec) - } - .value - } + override def filterA[G[_], A](fa: ArraySeq[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[ArraySeq[A]] = + fa.foldRight(Eval.now(G.pure(ArraySeq.untagged.empty[A]))) { + case (x, xse) => + G.map2Eval(f(x), xse)((b, vec) => if (b) x +: vec else vec) + } + .value + } } From 194d334c53896fb6c1f4f9a7bd0a63ccb1483f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 28 Jan 2020 11:34:16 -0500 Subject: [PATCH 3/6] Implementing tailRecM --- .../scala-2.13+/cats/instances/arraySeq.scala | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala index 9219aff56a..915aed8ee7 100644 --- a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala +++ b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala @@ -1,6 +1,7 @@ package cats package instances +import scala.annotation.tailrec import scala.collection.immutable.ArraySeq trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { @@ -72,8 +73,30 @@ object ArraySeqInstances { override def zipWithIndex[A](fa: ArraySeq[A]): ArraySeq[(A, Int)] = fa.zipWithIndex - def tailRecM[A, B](a: A)(fn: A => ArraySeq[Either[A, B]]): ArraySeq[B] = - ??? + def tailRecM[A, B](a: A)(fn: A => ArraySeq[Either[A, B]]): ArraySeq[B] = { + val buf = ArraySeq.untagged.newBuilder[B] + + @tailrec + def loop(state: List[Iterator[Either[A, B]]]): Unit = + state match { + case h :: tail if h.isEmpty => + loop(state = tail) + case h :: tail => + h.next match { + case Right(b) => + buf += b + loop(state) + case Left(a) => + loop(state = fn(a).iterator :: h :: tail) + } + + case Nil => () + } + + loop(state = fn(a).iterator :: Nil) + + buf.result() + } override def exists[A](fa: ArraySeq[A])(p: A => Boolean): Boolean = fa.exists(p) @@ -82,7 +105,7 @@ object ArraySeqInstances { fa.forall(p) override def get[A](fa: ArraySeq[A])(idx: Long): Option[A] = - if (idx < fa.length && idx.isValidInt) Some(fa(idx.toInt)) else None + if (idx >= 0 && idx < fa.length && idx.isValidInt) Some(fa(idx.toInt)) else None override def isEmpty[A](fa: ArraySeq[A]): Boolean = fa.isEmpty From fcb9e97258f86389a43032b0b78ed00ec4bb4e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 28 Jan 2020 12:34:59 -0500 Subject: [PATCH 4/6] Adding tests --- .../cats/tests/ArraySeqSuite.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala diff --git a/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala b/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala new file mode 100644 index 0000000000..bafab7a00a --- /dev/null +++ b/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala @@ -0,0 +1,33 @@ +package cats +package tests + +import cats.laws.discipline.{MonadTests, MonoidKTests, SerializableTests, TraverseFilterTests, TraverseTests} +import cats.laws.discipline.arbitrary._ +import cats.kernel.laws.discipline.{MonoidTests, OrderTests} + +import scala.collection.immutable.ArraySeq + +class ArraySeqSuite extends CatsSuite { + checkAll("ArraySeq[Int]", MonoidTests[ArraySeq[Int]].monoid) + checkAll("Monoid[ArraySeq]", SerializableTests.serializable(Monoid[ArraySeq[Int]])) + + checkAll("ArraySeq[Int]", OrderTests[ArraySeq[Int]].order) + checkAll("Order[ArraySeq]", SerializableTests.serializable(Order[ArraySeq[Int]])) + + checkAll("ArraySeq[Int]", MonadTests[ArraySeq].monad[Int, Int, Int]) + checkAll("Monad[ArraySeq]", SerializableTests.serializable(Monad[ArraySeq])) + + checkAll("ArraySeq[Int]", MonoidKTests[ArraySeq].monoidK[Int]) + checkAll("MonoidK[ArraySeq]", SerializableTests.serializable(MonoidK[ArraySeq])) + + checkAll("ArraySeq[Int] with Option", TraverseTests[ArraySeq].traverse[Int, Int, Int, Set[Int], Option, Option]) + checkAll("Traverse[ArraySeq]", SerializableTests.serializable(Traverse[ArraySeq])) + + checkAll("ArraySeq[Int]", TraverseFilterTests[ArraySeq].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[ArraySeq]", SerializableTests.serializable(TraverseFilter[ArraySeq])) + + test("show") { + ArraySeq(1, 2, 3).show should ===(s"ArraySeq(1, 2, 3)") + ArraySeq.empty[Int].show should ===(s"ArraySeq()") + } +} From e5f62440e8be04556f57b794dc631bba46f25188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 28 Jan 2020 12:37:16 -0500 Subject: [PATCH 5/6] Making the kernel instances as private as possible --- .../scala-2.13+/cats/instances/arraySeq.scala | 6 +- .../kernel/instances/ArraySeqInstances.scala | 135 +++++++++--------- 2 files changed, 71 insertions(+), 70 deletions(-) diff --git a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala index 915aed8ee7..5b234d185b 100644 --- a/core/src/main/scala-2.13+/cats/instances/arraySeq.scala +++ b/core/src/main/scala-2.13+/cats/instances/arraySeq.scala @@ -5,7 +5,7 @@ import scala.annotation.tailrec import scala.collection.immutable.ArraySeq trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { - implicit def catsStdInstancesForArraySeq: Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] = + implicit def catsStdInstancesForArraySeq: Monad[ArraySeq] with MonoidK[ArraySeq] with Traverse[ArraySeq] = ArraySeqInstances.stdInstances implicit def catsStdTraverseFilterForArraySeq: TraverseFilter[ArraySeq] = @@ -19,7 +19,7 @@ trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { object ArraySeqInstances { final private val stdInstances = - new Traverse[ArraySeq] with Monad[ArraySeq] with MonoidK[ArraySeq] { + new Monad[ArraySeq] with MonoidK[ArraySeq] with Traverse[ArraySeq] { def empty[A]: ArraySeq[A] = ArraySeq.untagged.empty @@ -27,7 +27,7 @@ object ArraySeqInstances { xs.concat(ys) override def algebra[A]: Monoid[ArraySeq[A]] = - new cats.kernel.instances.ArraySeqMonoid[A] + new cats.kernel.instances.ArraySeqInstances.ArraySeqMonoid def pure[A](a: A): ArraySeq[A] = ArraySeq.untagged.fill(n = 1)(elem = a) diff --git a/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala b/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala index 70914e6748..a159ff5a00 100644 --- a/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala +++ b/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala @@ -3,91 +3,92 @@ package instances import scala.annotation.tailrec import scala.collection.immutable.ArraySeq -import scala.reflect.ClassTag import compat.scalaVersionSpecific._ @suppressUnusedImportWarningForScalaVersionSpecific -trait ArraySeqInstances extends ArraySeqInstances1 { +trait ArraySeqInstances extends ArraySeqInstances.ArraySeqInstances1 { implicit def catsKernelStdOrderForArraySeq[A: Order]: Order[ArraySeq[A]] = - new ArraySeqOrder[A] + new ArraySeqInstances.ArraySeqOrder[A] - implicit def catsKernelStdMonoidForArraySeq[A: ClassTag]: Monoid[ArraySeq[A]] = - new ArraySeqMonoid[A] + implicit def catsKernelStdMonoidForArraySeq[A]: Monoid[ArraySeq[A]] = + new ArraySeqInstances.ArraySeqMonoid[A] } -private[cats] trait ArraySeqInstances1 extends ArraySeqInstances2 { - implicit def catsKernelStdPartialOrderForArraySeq[A: PartialOrder]: PartialOrder[ArraySeq[A]] = - new ArraySeqPartialOrder[A] +object ArraySeqInstances { + trait ArraySeqInstances1 extends ArraySeqInstances2 { + implicit def catsKernelStdPartialOrderForArraySeq[A: PartialOrder]: PartialOrder[ArraySeq[A]] = + new ArraySeqPartialOrder[A] - implicit def catsKernelStdHashForArraySeq[A: Hash]: Hash[ArraySeq[A]] = - new ArraySeqHash[A] -} + implicit def catsKernelStdHashForArraySeq[A: Hash]: Hash[ArraySeq[A]] = + new ArraySeqHash[A] + } -private[cats] trait ArraySeqInstances2 { - implicit def catsKernelStdEqForArraySeq[A: Eq]: Eq[ArraySeq[A]] = - new ArraySeqEq[A] -} + trait ArraySeqInstances2 { + implicit def catsKernelStdEqForArraySeq[A: Eq]: Eq[ArraySeq[A]] = + new ArraySeqEq[A] + } -final private[cats] class ArraySeqOrder[A](implicit ev: Order[A]) extends Order[ArraySeq[A]] { - def compare(xs: ArraySeq[A], ys: ArraySeq[A]): Int = { - @tailrec def loop(i: Int): Int = - (i < xs.length, i < ys.length) match { - case (true, true) => - val n = ev.compare(xs(i), ys(i)) - if (n != 0) n else loop(i + 1) - case (true, false) => 1 - case (false, true) => -1 - case (false, false) => 0 - } - - if (xs eq ys) 0 else loop(i = 0) + final private class ArraySeqOrder[A](implicit ev: Order[A]) extends Order[ArraySeq[A]] { + def compare(xs: ArraySeq[A], ys: ArraySeq[A]): Int = { + @tailrec def loop(i: Int): Int = + (i < xs.length, i < ys.length) match { + case (true, true) => + val n = ev.compare(xs(i), ys(i)) + if (n != 0) n else loop(i + 1) + case (true, false) => 1 + case (false, true) => -1 + case (false, false) => 0 + } + + if (xs eq ys) 0 else loop(i = 0) + } } -} -private[cats] class ArraySeqPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[ArraySeq[A]] { - def partialCompare(xs: ArraySeq[A], ys: ArraySeq[A]): Double = { - @tailrec def loop(i: Int): Double = - (i < xs.length, i < ys.length) match { - case (true, true) => - val n = ev.partialCompare(xs(i), ys(i)) - if (n != 0) n else loop(i + 1) - case (true, false) => 1 - case (false, true) => -1 - case (false, false) => 0 - } - - if (xs eq ys) 0.0 else loop(i = 0) + private class ArraySeqPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[ArraySeq[A]] { + def partialCompare(xs: ArraySeq[A], ys: ArraySeq[A]): Double = { + @tailrec def loop(i: Int): Double = + (i < xs.length, i < ys.length) match { + case (true, true) => + val n = ev.partialCompare(xs(i), ys(i)) + if (n != 0) n else loop(i + 1) + case (true, false) => 1 + case (false, true) => -1 + case (false, false) => 0 + } + + if (xs eq ys) 0.0 else loop(i = 0) + } } -} -private[cats] class ArraySeqHash[A](implicit ev: Hash[A]) extends ArraySeqEq[A]()(ev) with Hash[ArraySeq[A]] { - def hash(xs: ArraySeq[A]): Int = StaticMethods.orderedHash(xs) -} + private class ArraySeqHash[A](implicit ev: Hash[A]) extends ArraySeqEq[A]()(ev) with Hash[ArraySeq[A]] { + def hash(xs: ArraySeq[A]): Int = StaticMethods.orderedHash(xs) + } -private[cats] class ArraySeqEq[A](implicit ev: Eq[A]) extends Eq[ArraySeq[A]] { - def eqv(xs: ArraySeq[A], ys: ArraySeq[A]): Boolean = { - @tailrec def loop(i: Int): Boolean = - (i < xs.length, i < ys.length) match { - case (true, true) => if (ev.eqv(xs(i), ys(i))) loop(i + 1) else false - case (true, false) => false - case (false, true) => false - case (false, false) => true - } - - (xs eq ys) || loop(i = 0) + private class ArraySeqEq[A](implicit ev: Eq[A]) extends Eq[ArraySeq[A]] { + def eqv(xs: ArraySeq[A], ys: ArraySeq[A]): Boolean = { + @tailrec def loop(i: Int): Boolean = + (i < xs.length, i < ys.length) match { + case (true, true) => if (ev.eqv(xs(i), ys(i))) loop(i + 1) else false + case (true, false) => false + case (false, true) => false + case (false, false) => true + } + + (xs eq ys) || loop(i = 0) + } } -} -final private[cats] class ArraySeqMonoid[A] extends Monoid[ArraySeq[A]] { - def empty: ArraySeq[A] = - ArraySeq.untagged.empty + final private[cats] class ArraySeqMonoid[A] extends Monoid[ArraySeq[A]] { + def empty: ArraySeq[A] = + ArraySeq.untagged.empty - def combine(xs: ArraySeq[A], ys: ArraySeq[A]): ArraySeq[A] = - xs.concat(ys) + def combine(xs: ArraySeq[A], ys: ArraySeq[A]): ArraySeq[A] = + xs.concat(ys) - override def combineN(x: ArraySeq[A], n: Int): ArraySeq[A] = - StaticMethods.combineNIterable(ArraySeq.untagged.newBuilder[A], x, n) + override def combineN(x: ArraySeq[A], n: Int): ArraySeq[A] = + StaticMethods.combineNIterable(ArraySeq.untagged.newBuilder[A], x, n) - override def combineAll(xs: IterableOnce[ArraySeq[A]]): ArraySeq[A] = - StaticMethods.combineAllIterable(ArraySeq.untagged.newBuilder[A], xs) + override def combineAll(xs: IterableOnce[ArraySeq[A]]): ArraySeq[A] = + StaticMethods.combineAllIterable(ArraySeq.untagged.newBuilder[A], xs) + } } From 0ab2b59e0cd39c5560ebe9d3a41813c7474838c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 28 Jan 2020 14:24:52 -0500 Subject: [PATCH 6/6] Adding more tests --- .../kernel/instances/ArraySeqInstances.scala | 8 +++--- .../cats/tests/ArraySeqSuite.scala | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala b/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala index a159ff5a00..09a242f341 100644 --- a/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala +++ b/kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala @@ -29,7 +29,7 @@ object ArraySeqInstances { } final private class ArraySeqOrder[A](implicit ev: Order[A]) extends Order[ArraySeq[A]] { - def compare(xs: ArraySeq[A], ys: ArraySeq[A]): Int = { + final def compare(xs: ArraySeq[A], ys: ArraySeq[A]): Int = { @tailrec def loop(i: Int): Int = (i < xs.length, i < ys.length) match { case (true, true) => @@ -45,7 +45,7 @@ object ArraySeqInstances { } private class ArraySeqPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[ArraySeq[A]] { - def partialCompare(xs: ArraySeq[A], ys: ArraySeq[A]): Double = { + final def partialCompare(xs: ArraySeq[A], ys: ArraySeq[A]): Double = { @tailrec def loop(i: Int): Double = (i < xs.length, i < ys.length) match { case (true, true) => @@ -61,11 +61,11 @@ object ArraySeqInstances { } private class ArraySeqHash[A](implicit ev: Hash[A]) extends ArraySeqEq[A]()(ev) with Hash[ArraySeq[A]] { - def hash(xs: ArraySeq[A]): Int = StaticMethods.orderedHash(xs) + final def hash(xs: ArraySeq[A]): Int = StaticMethods.orderedHash(xs) } private class ArraySeqEq[A](implicit ev: Eq[A]) extends Eq[ArraySeq[A]] { - def eqv(xs: ArraySeq[A], ys: ArraySeq[A]): Boolean = { + final def eqv(xs: ArraySeq[A], ys: ArraySeq[A]): Boolean = { @tailrec def loop(i: Int): Boolean = (i < xs.length, i < ys.length) match { case (true, true) => if (ev.eqv(xs(i), ys(i))) loop(i + 1) else false diff --git a/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala b/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala index bafab7a00a..5aef9eb2cf 100644 --- a/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala +++ b/tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala @@ -1,9 +1,9 @@ package cats package tests +import cats.kernel.laws.discipline.{EqTests, HashTests, MonoidTests, OrderTests, PartialOrderTests} import cats.laws.discipline.{MonadTests, MonoidKTests, SerializableTests, TraverseFilterTests, TraverseTests} import cats.laws.discipline.arbitrary._ -import cats.kernel.laws.discipline.{MonoidTests, OrderTests} import scala.collection.immutable.ArraySeq @@ -26,8 +26,32 @@ class ArraySeqSuite extends CatsSuite { checkAll("ArraySeq[Int]", TraverseFilterTests[ArraySeq].traverseFilter[Int, Int, Int]) checkAll("TraverseFilter[ArraySeq]", SerializableTests.serializable(TraverseFilter[ArraySeq])) + { + implicit val eqv: Eq[ListWrapper[Int]] = ListWrapper.eqv[Int] + checkAll("ArraySeq[Int]", EqTests[ArraySeq[ListWrapper[Int]]].eqv) + checkAll("Eq[ArraySeq]", SerializableTests.serializable(Eq[ArraySeq[ListWrapper[Int]]])) + } + + { + implicit val partialOrder: PartialOrder[ListWrapper[Int]] = ListWrapper.partialOrder[Int] + checkAll("ArraySeq[Int]", PartialOrderTests[ArraySeq[ListWrapper[Int]]].partialOrder) + checkAll("PartialOrder[ArraySeq]", SerializableTests.serializable(PartialOrder[ArraySeq[ListWrapper[Int]]])) + } + + { + implicit val hash: Hash[ListWrapper[Int]] = ListWrapper.hash[Int] + checkAll("ArraySeq[Int]", HashTests[ArraySeq[ListWrapper[Int]]].hash) + checkAll("Hash[ArraySeq]", SerializableTests.serializable(Hash[ArraySeq[ListWrapper[Int]]])) + } + test("show") { ArraySeq(1, 2, 3).show should ===(s"ArraySeq(1, 2, 3)") ArraySeq.empty[Int].show should ===(s"ArraySeq()") } + + test("MonoidK.algebra consistent with Monoid") { + forAll { (xs: ArraySeq[Int], ys: ArraySeq[Int]) => + MonoidK[ArraySeq].algebra[Int].combine(xs, ys) should ===(Monoid[ArraySeq[Int]].combine(xs, ys)) + } + } }