Skip to content

Commit

Permalink
Add proper Eq, PartialOrder, Order and Coflatmap instances
Browse files Browse the repository at this point in the history
  • Loading branch information
Luka Jacobowitz committed Aug 15, 2018
1 parent 76174a7 commit bc66361
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 8 deletions.
90 changes: 84 additions & 6 deletions core/src/main/scala/cats/data/Chain.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cats
package data

import cats.implicits._
import Chain._

import scala.annotation.tailrec
import scala.collection.immutable.SortedMap
import scala.collection.mutable.ListBuffer

/**
* Trivial catenable sequence. Supports O(1) append, and (amortized)
Expand Down Expand Up @@ -345,6 +345,27 @@ sealed abstract class Chain[+A] {
final def toVector: Vector[A] =
iterator.toVector

/**
* Typesafe equality operator.
*
* This method is similar to == except that it only allows two
* Chain[A] values to be compared to each other, and uses
* equality provided by Eq[_] instances, rather than using the
* universal equality provided by .equals.
*/
def ===[AA >: A](that: Chain[AA])(implicit A: Eq[AA]): Boolean =
(this eq that) || {
val iterX = iterator
val iterY = that.iterator
while (iterX.hasNext && iterY.hasNext) {
// scalastyle:off return
if (!A.eqv(iterX.next, iterY.next)) return false
// scalastyle:on return
}

iterX.hasNext == iterY.hasNext
}

def show[AA >: A](implicit AA: Show[AA]): String = {
val builder = new StringBuilder("Chain(")
var first = true
Expand Down Expand Up @@ -508,14 +529,15 @@ object Chain extends ChainInstances {
// scalastyle:on null
}

private[data] sealed abstract class ChainInstances {
private[data] sealed abstract class ChainInstances extends ChainInstances1 {
implicit def catsDataMonoidForChain[A]: Monoid[Chain[A]] = new Monoid[Chain[A]] {
def empty: Chain[A] = Chain.nil
def combine(c: Chain[A], c2: Chain[A]): Chain[A] = Chain.concat(c, c2)
}

implicit val catsDataInstancesForChain: Traverse[Chain] with Alternative[Chain] with Monad[Chain] =
new Traverse[Chain] with Alternative[Chain] with Monad[Chain] {
implicit val catsDataInstancesForChain: Traverse[Chain] with Alternative[Chain]
with Monad[Chain] with CoflatMap[Chain] =
new Traverse[Chain] with Alternative[Chain] with Monad[Chain] with CoflatMap[Chain] {
def foldLeft[A, B](fa: Chain[A], b: B)(f: (B, A) => B): B =
fa.foldLeft(b)(f)
def foldRight[A, B](fa: Chain[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Expand All @@ -530,6 +552,16 @@ private[data] sealed abstract class ChainInstances {
override def forall[A](fa: Chain[A])(p: A => Boolean): Boolean = fa.forall(p)
override def find[A](fa: Chain[A])(f: A => Boolean): Option[A] = fa.find(f)

def coflatMap[A, B](fa: Chain[A])(f: Chain[A] => B): Chain[B] = {
@tailrec def go(as: Chain[A], res: ListBuffer[B]): Chain[B] =
as.uncons match {
case Some((h, t)) => go(t, res += f(t))
case None => Chain.fromSeq(res.result())
}

go(fa, ListBuffer.empty)
}

def traverse[G[_], A, B](fa: Chain[A])(f: A => G[B])(implicit G: Applicative[G]): G[Chain[B]] =
fa.foldRight[G[Chain[B]]](G.pure(nil)) { (a, gcatb) =>
G.map2(f(a), gcatb)(_ +: _)
Expand Down Expand Up @@ -566,9 +598,55 @@ private[data] sealed abstract class ChainInstances {
implicit def catsDataShowForChain[A](implicit A: Show[A]): Show[Chain[A]] =
Show.show[Chain[A]](_.show)

implicit def catsDataOrderForChain[A](implicit A0: Order[A]): Order[Chain[A]] =
new Order[Chain[A]] with ChainPartialOrder[A] {
implicit def A: PartialOrder[A] = A0
def compare(x: Chain[A], y: Chain[A]): Int = if (x eq y) 0 else {
val iterX = x.iterator
val iterY = y.iterator
while (iterX.hasNext && iterY.hasNext) {
val n = A0.compare(iterX.next, iterY.next)
// scalastyle:off return
if (n != 0) return n
// scalastyle:on return
}

if (iterX.hasNext) 1
else if (iterY.hasNext) -1
else 0
}
}

}

private[data] sealed abstract class ChainInstances1 extends ChainInstances2 {
implicit def catsDataPartialOrderForChain[A](implicit A0: PartialOrder[A]): PartialOrder[Chain[A]] =
new ChainPartialOrder[A] { implicit def A: PartialOrder[A] = A0 }
}

private[data] sealed abstract class ChainInstances2 {
implicit def catsDataEqForChain[A](implicit A: Eq[A]): Eq[Chain[A]] = new Eq[Chain[A]] {
def eqv(x: Chain[A], y: Chain[A]): Boolean =
(x eq y) || x.toList === y.toList
def eqv(x: Chain[A], y: Chain[A]): Boolean = x === y
}
}

private[data] trait ChainPartialOrder[A] extends PartialOrder[Chain[A]] {
implicit def A: PartialOrder[A]

override def partialCompare(x: Chain[A], y: Chain[A]): Double = if (x eq y) 0.0 else {
val iterX = x.iterator
val iterY = y.iterator
while (iterX.hasNext && iterY.hasNext) {
val n = A.partialCompare(iterX.next, iterY.next)
// scalastyle:off return
if (n != 0.0) return n
// scalastyle:on return
}

if (iterX.hasNext) 1.0
else if (iterY.hasNext) -1.0
else 0.0
}

override def eqv(x: Chain[A], y: Chain[A]): Boolean = x === y
}
3 changes: 3 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ object arbitrary extends ArbitraryInstances0 {
}
})

implicit def catsLawsCogenForChain[A](implicit A: Cogen[A]): Cogen[Chain[A]] =
Cogen[List[A]].contramap(_.toList)

}

private[discipline] sealed trait ArbitraryInstances0 {
Expand Down
10 changes: 8 additions & 2 deletions tests/src/test/scala/cats/tests/ChainSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package cats
package tests

import cats.data.Chain
import cats.kernel.laws.discipline.MonoidTests
import cats.laws.discipline.{AlternativeTests, MonadTests, SerializableTests, TraverseTests}
import cats.kernel.laws.discipline.{MonoidTests, OrderTests}
import cats.laws.discipline.{AlternativeTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests}
import cats.laws.discipline.arbitrary._

class ChainSuite extends CatsSuite {
Expand All @@ -16,9 +16,15 @@ class ChainSuite extends CatsSuite {
checkAll("Chain[Int]", MonadTests[Chain].monad[Int, Int, Int])
checkAll("Monad[Chain]", SerializableTests.serializable(Monad[Chain]))

checkAll("Chain[Int]", CoflatMapTests[Chain].coflatMap[Int, Int, Int])
checkAll("Coflatmap[Chain]", SerializableTests.serializable(CoflatMap[Chain]))

checkAll("Chain[Int]", MonoidTests[Chain[Int]].monoid)
checkAll("Monoid[Chain]", SerializableTests.serializable(Monoid[Chain[Int]]))

checkAll("Chain[Int]", OrderTests[Chain[Int]].order)
checkAll("Order[Chain]", SerializableTests.serializable(Order[Chain[Int]]))

test("show"){
Show[Chain[Int]].show(Chain(1, 2, 3)) should === ("Chain(1, 2, 3)")
Chain.empty[Int].show should === ("Chain()")
Expand Down

0 comments on commit bc66361

Please sign in to comment.