Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding instances for ArraySeq #3273

Merged
merged 6 commits into from
Jan 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/scala-2.13+/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ abstract class AllInstancesBinCompat

trait AllInstances
extends AnyValInstances
with ArraySeqInstances
with BigIntInstances
with BigDecimalInstances
with BitSetInstances
Expand Down
177 changes: 177 additions & 0 deletions core/src/main/scala-2.13+/cats/instances/arraySeq.scala
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
}
}
1 change: 1 addition & 0 deletions core/src/main/scala-2.13+/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package cats.kernel
package instances

trait AllInstances
extends BigDecimalInstances
extends ArraySeqInstances
with BigDecimalInstances
with BigIntInstances
with BitSetInstances
with BooleanInstances
Expand Down
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)
}
}
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
57 changes: 57 additions & 0 deletions tests/src/test/scala-2.13+/cats/tests/ArraySeqSuite.scala
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))
}
}
}