-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding instances for ArraySeq (#3273)
* Adding implementation * Fixing binary incompability issue * Implementing tailRecM * Adding tests * Making the kernel instances as private as possible * Adding more tests
- Loading branch information
1 parent
93ebced
commit e9ff056
Showing
7 changed files
with
336 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
core/src/main/scala-2.13+/cats/instances/arraySeq.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package cats | ||
package instances | ||
|
||
import scala.annotation.tailrec | ||
import scala.collection.immutable.ArraySeq | ||
|
||
trait ArraySeqInstances extends cats.kernel.instances.ArraySeqInstances { | ||
implicit def catsStdInstancesForArraySeq: Monad[ArraySeq] with MonoidK[ArraySeq] with Traverse[ArraySeq] = | ||
ArraySeqInstances.stdInstances | ||
|
||
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 Monad[ArraySeq] with MonoidK[ArraySeq] with Traverse[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.ArraySeqInstances.ArraySeqMonoid | ||
|
||
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] = { | ||
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) | ||
|
||
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 >= 0 && 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)) | ||
} | ||
|
||
final private val stdTraverseFilterInstance = | ||
new TraverseFilter[ArraySeq] { | ||
val traverse: Traverse[ArraySeq] = stdInstances | ||
|
||
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
kernel/src/main/scala-2.13+/cats/kernel/instances/ArraySeqInstances.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package cats.kernel | ||
package instances | ||
|
||
import scala.annotation.tailrec | ||
import scala.collection.immutable.ArraySeq | ||
import compat.scalaVersionSpecific._ | ||
|
||
@suppressUnusedImportWarningForScalaVersionSpecific | ||
trait ArraySeqInstances extends ArraySeqInstances.ArraySeqInstances1 { | ||
implicit def catsKernelStdOrderForArraySeq[A: Order]: Order[ArraySeq[A]] = | ||
new ArraySeqInstances.ArraySeqOrder[A] | ||
|
||
implicit def catsKernelStdMonoidForArraySeq[A]: Monoid[ArraySeq[A]] = | ||
new ArraySeqInstances.ArraySeqMonoid[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] | ||
} | ||
|
||
trait ArraySeqInstances2 { | ||
implicit def catsKernelStdEqForArraySeq[A: Eq]: Eq[ArraySeq[A]] = | ||
new ArraySeqEq[A] | ||
} | ||
|
||
final private class ArraySeqOrder[A](implicit ev: Order[A]) extends Order[ArraySeq[A]] { | ||
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) => | ||
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 class ArraySeqPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[ArraySeq[A]] { | ||
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) => | ||
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 ArraySeqHash[A](implicit ev: Hash[A]) extends ArraySeqEq[A]()(ev) with Hash[ArraySeq[A]] { | ||
final def hash(xs: ArraySeq[A]): Int = StaticMethods.orderedHash(xs) | ||
} | ||
|
||
private class ArraySeqEq[A](implicit ev: Eq[A]) extends Eq[ArraySeq[A]] { | ||
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 | ||
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) | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
kernel/src/main/scala-2.13+/cats/kernel/instances/arraySeq/package.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package cats.kernel | ||
package instances | ||
|
||
package object arraySeq extends ArraySeqInstances // scalastyle:ignore package.object.name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
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 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])) | ||
|
||
{ | ||
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)) | ||
} | ||
} | ||
} |