From f66b99750ff85f6bfd71325fbe425376bc4cde72 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 31 Aug 2017 02:32:27 -0400 Subject: [PATCH 1/9] Add some instances we were missing. This commit adds support for some types: - Ordering, PartialOrdering, and Equiv - scala.concurrent.duration.Duration - scala.collection.immutable.Queue The ordering types should be relatively uncontroversial. I chose to support Duration even though arguably FiniteDuration is a bit better behaved. This isn't the worst group we support, and I feel like it's possible that folks will really benefit from these instances. Queue is probably the most interesting immutable data structure we didn't already support. I based its instances on those for List, rewriting things to use .isEmpty and .dequeue instead of pattern-matching. We could potentially try to support some of the other concrete collection types (e.g. HashMap, TreeMap, IntMap, SortedMap, etc.) which might not be a bad idea. I haven't done that here. I added tests for Queue and Duration, but have not (yet) tested the pretty trivial contravariant instances. --- core/src/main/scala/cats/instances/all.scala | 26 ++-- .../main/scala/cats/instances/duration.scala | 9 ++ .../src/main/scala/cats/instances/equiv.scala | 18 +++ .../main/scala/cats/instances/ordering.scala | 16 ++ .../main/scala/cats/instances/package.scala | 72 ++++----- .../cats/instances/partialOrdering.scala | 19 +++ .../src/main/scala/cats/instances/queue.scala | 145 ++++++++++++++++++ .../scala/cats/kernel/laws/LawTests.scala | 67 ++++++-- .../scala/cats/kernel/instances/all.scala | 2 + .../cats/kernel/instances/duration.scala | 32 ++++ .../scala/cats/kernel/instances/queue.scala | 52 +++++++ .../test/scala/cats/tests/QueueTests.scala | 28 ++++ 12 files changed, 426 insertions(+), 60 deletions(-) create mode 100644 core/src/main/scala/cats/instances/duration.scala create mode 100644 core/src/main/scala/cats/instances/equiv.scala create mode 100644 core/src/main/scala/cats/instances/ordering.scala create mode 100644 core/src/main/scala/cats/instances/partialOrdering.scala create mode 100644 core/src/main/scala/cats/instances/queue.scala create mode 100644 kernel/src/main/scala/cats/kernel/instances/duration.scala create mode 100644 kernel/src/main/scala/cats/kernel/instances/queue.scala create mode 100644 tests/src/test/scala/cats/tests/QueueTests.scala diff --git a/core/src/main/scala/cats/instances/all.scala b/core/src/main/scala/cats/instances/all.scala index 9a52de3f6c..d974ee5d6c 100644 --- a/core/src/main/scala/cats/instances/all.scala +++ b/core/src/main/scala/cats/instances/all.scala @@ -2,26 +2,30 @@ package cats package instances trait AllInstances - extends FunctionInstances - with StringInstances + extends AnyValInstances + with BigIntInstances + with BigDecimalInstances + with BitSetInstances with EitherInstances with EqInstances + with EquivInstances + with FunctionInstances + with FutureInstances with ListInstances + with MapInstances + with MonoidInstances with OptionInstances with OrderInstances - with MonoidInstances + with OrderingInstances with PartialOrderInstances + with PartialOrderingInstances + with QueueInstances with SemigroupInstances with SetInstances with StreamInstances - with VectorInstances - with AnyValInstances - with MapInstances - with BigIntInstances - with BigDecimalInstances - with BitSetInstances - with FutureInstances + with StringInstances + with SymbolInstances with TryInstances with TupleInstances with UUIDInstances - with SymbolInstances + with VectorInstances diff --git a/core/src/main/scala/cats/instances/duration.scala b/core/src/main/scala/cats/instances/duration.scala new file mode 100644 index 0000000000..2cc6f1b940 --- /dev/null +++ b/core/src/main/scala/cats/instances/duration.scala @@ -0,0 +1,9 @@ +package cats +package instances + +import scala.concurrent.duration.Duration + +trait DurationInstances extends cats.kernel.instances.DurationInstances { + implicit val catsStdShowForDuration: Show[Duration] = + Show.fromToString[Duration] +} diff --git a/core/src/main/scala/cats/instances/equiv.scala b/core/src/main/scala/cats/instances/equiv.scala new file mode 100644 index 0000000000..f3ef2bc1e0 --- /dev/null +++ b/core/src/main/scala/cats/instances/equiv.scala @@ -0,0 +1,18 @@ +package cats +package instances + +trait EquivInstances { + implicit val catsContravariantCartesianEquiv: ContravariantCartesian[Equiv] = + new ContravariantCartesian[Equiv] { + def contramap[A, B](fa: Equiv[A])(f: B => A): Equiv[B] = + new Equiv[B] { + def equiv(x: B, y: B): Boolean = fa.equiv(f(x), f(y)) + } + + def product[A, B](fa: Equiv[A], fb: Equiv[B]): Equiv[(A, B)] = + new Equiv[(A, B)] { + def equiv(x: (A, B), y: (A, B)): Boolean = + fa.equiv(x._1, y._1) && fb.equiv(x._2, y._2) + } + } +} diff --git a/core/src/main/scala/cats/instances/ordering.scala b/core/src/main/scala/cats/instances/ordering.scala new file mode 100644 index 0000000000..1ef599e127 --- /dev/null +++ b/core/src/main/scala/cats/instances/ordering.scala @@ -0,0 +1,16 @@ +package cats +package instances + +import cats.functor.Contravariant + +trait OrderingInstances { + + implicit val catsFunctorContravariantForOrdering: Contravariant[Ordering] = + new Contravariant[Ordering] { + /** Derive an `Ordering` for `B` given an `Ordering[A]` and a function `B => A`. + * + * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) + */ + def contramap[A, B](fa: Ordering[A])(f: B => A): Ordering[B] = fa.on(f) + } +} diff --git a/core/src/main/scala/cats/instances/package.scala b/core/src/main/scala/cats/instances/package.scala index c8793a116c..85dadb037c 100644 --- a/core/src/main/scala/cats/instances/package.scala +++ b/core/src/main/scala/cats/instances/package.scala @@ -1,40 +1,40 @@ package cats package object instances { - object all extends AllInstances - - object either extends EitherInstances - object eq extends EqInstances - object function extends FunctionInstances - object order extends OrderInstances - object partialOrder extends PartialOrderInstances - object monoid extends MonoidInstances - object semigroup extends SemigroupInstances - - object list extends ListInstances - object option extends OptionInstances - object set extends SetInstances - object bitSet extends BitSetInstances - object stream extends StreamInstances - object vector extends VectorInstances - object map extends MapInstances - object future extends FutureInstances - - object string extends StringInstances - object int extends IntInstances - object byte extends ByteInstances - object long extends LongInstances - object char extends CharInstances - object short extends ShortInstances - object float extends FloatInstances - object double extends DoubleInstances - object boolean extends BooleanInstances - object unit extends UnitInstances - - object bigInt extends BigIntInstances - object bigDecimal extends BigDecimalInstances - - object try_ extends TryInstances - object tuple extends TupleInstances - object uuid extends UUIDInstances + object all extends AllInstances + object bigInt extends BigIntInstances + object bigDecimal extends BigDecimalInstances + object bitSet extends BitSetInstances + object boolean extends BooleanInstances + object byte extends ByteInstances + object char extends CharInstances + object double extends DoubleInstances + object duration extends DurationInstances + object either extends EitherInstances + object eq extends EqInstances + object equiv extends EquivInstances + object float extends FloatInstances + object function extends FunctionInstances + object future extends FutureInstances + object int extends IntInstances + object list extends ListInstances + object long extends LongInstances + object map extends MapInstances + object monoid extends MonoidInstances + object option extends OptionInstances + object order extends OrderInstances + object ordering extends OrderingInstances + object partialOrder extends PartialOrderInstances + object partialOrdering extends PartialOrderingInstances + object queue extends QueueInstances + object semigroup extends SemigroupInstances + object set extends SetInstances + object short extends ShortInstances + object stream extends StreamInstances + object string extends StringInstances + object try_ extends TryInstances + object tuple extends TupleInstances + object unit extends UnitInstances + object uuid extends UUIDInstances + object vector extends VectorInstances } diff --git a/core/src/main/scala/cats/instances/partialOrdering.scala b/core/src/main/scala/cats/instances/partialOrdering.scala new file mode 100644 index 0000000000..05f7174720 --- /dev/null +++ b/core/src/main/scala/cats/instances/partialOrdering.scala @@ -0,0 +1,19 @@ +package cats +package instances + +import cats.functor.Contravariant + +trait PartialOrderingInstances { + implicit val catsFunctorContravariantForPartialOrdering: Contravariant[PartialOrdering] = + new Contravariant[PartialOrdering] { + /** Derive a `PartialOrdering` for `B` given a `PartialOrdering[A]` and a function `B => A`. + * + * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) + */ + def contramap[A, B](fa: PartialOrdering[A])(f: B => A): PartialOrdering[B] = + new PartialOrdering[B] { + def lteq(x: B, y: B): Boolean = fa.lteq(f(x), f(y)) + def tryCompare(x: B, y: B): Option[Int] = fa.tryCompare(f(x), f(y)) + } + } +} diff --git a/core/src/main/scala/cats/instances/queue.scala b/core/src/main/scala/cats/instances/queue.scala new file mode 100644 index 0000000000..f51969da44 --- /dev/null +++ b/core/src/main/scala/cats/instances/queue.scala @@ -0,0 +1,145 @@ +package cats +package instances + +import cats.syntax.show._ + +import scala.annotation.tailrec +import scala.collection.immutable.Queue +import scala.util.Try + +trait QueueInstances extends cats.kernel.instances.QueueInstances { + + implicit val catsStdInstancesForQueue: Traverse[Queue] with Alternative[Queue] with Monad[Queue] with CoflatMap[Queue] = + new Traverse[Queue] with Alternative[Queue] with Monad[Queue] with CoflatMap[Queue] { + def empty[A]: Queue[A] = Queue.empty + + def combineK[A](x: Queue[A], y: Queue[A]): Queue[A] = x ++ y + + def pure[A](x: A): Queue[A] = Queue(x) + + override def map[A, B](fa: Queue[A])(f: A => B): Queue[B] = + fa.map(f) + + def flatMap[A, B](fa: Queue[A])(f: A => Queue[B]): Queue[B] = + fa.flatMap(f) + + override def map2[A, B, Z](fa: Queue[A], fb: Queue[B])(f: (A, B) => Z): Queue[Z] = + if (fb.isEmpty) Queue.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: Queue[A], fb: Eval[Queue[B]])(f: (A, B) => Z): Eval[Queue[Z]] = + if (fa.isEmpty) Eval.now(Queue.empty) // no need to evaluate fb + else fb.map(fb => map2(fa, fb)(f)) + + def tailRecM[A, B](a: A)(f: A => Queue[Either[A, B]]): Queue[B] = { + val bldr = Queue.newBuilder[B] + @tailrec def go(lists: List[Queue[Either[A, B]]]): Queue[B] = + lists match { + case q :: tail => + if (q.isEmpty) go(tail) + else { + val (e, es) = q.dequeue + e match { + case Right(b) => bldr += b; go(es :: tail) + case Left(a) => go(f(a) :: es :: tail) + } + } + case Nil => + bldr.result + } + go(f(a) :: Nil) + } + + def coflatMap[A, B](fa: Queue[A])(f: Queue[A] => B): Queue[B] = { + val bldr = Queue.newBuilder[B] + @tailrec def loop(as: Queue[A]): Queue[B] = + if (as.isEmpty) bldr.result + else { + val (_, rest) = as.dequeue + bldr += f(as) + loop(rest) + } + loop(fa) + } + + def foldLeft[A, B](fa: Queue[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: Queue[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(as: Queue[A]): Eval[B] = + if (as.isEmpty) lb + else { + val (h, t) = as.dequeue + f(h, Eval.defer(loop(t))) + } + Eval.defer(loop(fa)) + } + + def traverse[G[_], A, B](fa: Queue[A])(f: A => G[B])(implicit G: Applicative[G]): G[Queue[B]] = + foldRight[A, G[Queue[B]]](fa, Always(G.pure(Queue.empty))){ (a, lglb) => + G.map2Eval(f(a), lglb)(_ +: _) + }.value + + override def mapWithIndex[A, B](fa: Queue[A])(f: (A, Int) => B): Queue[B] = { + val b = Queue.newBuilder[B] + fa.iterator.zipWithIndex.map(ai => f(ai._1, ai._2)).foreach(b += _) + b.result + } + + override def zipWithIndex[A](fa: Queue[A]): Queue[(A, Int)] = + fa.zipWithIndex + + override def get[A](fa: Queue[A])(idx: Long): Option[A] = + if (idx < 0 || idx > Int.MaxValue) None + else Try(fa(idx.toInt)).toOption + + override def exists[A](fa: Queue[A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def forall[A](fa: Queue[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def isEmpty[A](fa: Queue[A]): Boolean = fa.isEmpty + + override def foldM[G[_], A, B](fa: Queue[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = { + def step(in: (Queue[A], B)): G[Either[(Queue[A], B), B]] = { + val (xs, b) = in + if (xs.isEmpty) G.pure(Right(b)) + else { + val (a, tail) = xs.dequeue + G.map(f(b, a)) { bnext => Left((tail, bnext)) } + } + } + + G.tailRecM((fa, z))(step) + } + + override def fold[A](fa: Queue[A])(implicit A: Monoid[A]): A = A.combineAll(fa) + + override def toList[A](fa: Queue[A]): List[A] = fa.toList + + override def reduceLeftOption[A](fa: Queue[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: Queue[A])(f: A => Boolean): Option[A] = + fa.find(f) + + override def filter_[A](fa: Queue[A])(p: A => Boolean): List[A] = + fa.iterator.filter(p).toList + + override def takeWhile_[A](fa: Queue[A])(p: A => Boolean): List[A] = + fa.iterator.takeWhile(p).toList + + override def dropWhile_[A](fa: Queue[A])(p: A => Boolean): List[A] = + fa.iterator.dropWhile(p).toList + + override def algebra[A]: Monoid[Queue[A]] = + new kernel.instances.QueueMonoid[A] + } + + implicit def catsStdShowForQueue[A:Show]: Show[Queue[A]] = + new Show[Queue[A]] { + def show(fa: Queue[A]): String = + fa.iterator.map(_.show).mkString("Queue(", ", ", ")") + } +} diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index 769970c6b7..2a6a854176 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -13,22 +13,14 @@ import Arbitrary.arbitrary import org.scalactic.anyvals.{ PosInt, PosZInt } import org.scalatest.FunSuite +import scala.concurrent.duration.Duration +import scala.collection.immutable.{BitSet, Queue} import scala.util.Random -import scala.collection.immutable.BitSet import java.util.UUID +import java.util.concurrent.TimeUnit.{DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS} -class LawTests extends FunSuite with Discipline { - - // The scalacheck defaults (100,100) are too high for scala-js. - final val PropMaxSize: PosZInt = if (Platform.isJs) 10 else 100 - final val PropMinSuccessful: PosInt = if (Platform.isJs) 10 else 100 - - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration(minSuccessful = PropMinSuccessful, sizeRange = PropMaxSize) - - implicit def orderLaws[A: Cogen: Eq: Arbitrary]: OrderLaws[A] = OrderLaws[A] - implicit def groupLaws[A: Cogen: Eq: Arbitrary]: GroupLaws[A] = GroupLaws[A] +object KernelCheck { implicit val arbitraryBitSet: Arbitrary[BitSet] = Arbitrary(arbitrary[List[Short]].map(ns => BitSet(ns.map(_ & 0xffff): _*))) @@ -39,6 +31,20 @@ class LawTests extends FunSuite with Discipline { implicit val arbitraryUUID: Arbitrary[UUID] = Arbitrary(Gen.uuid) + implicit val arbitraryDuration: Arbitrary[Duration] = { + // max range is +/- 292 years, but we give ourselves some extra headroom + // to ensure that we can add these things up. they crash on overflow. + val n = (292L * 365) / 50 + Arbitrary(Gen.oneOf( + Gen.choose(-n, n).map(Duration(_, DAYS)), + Gen.choose(-n * 24L, n * 24L).map(Duration(_, HOURS)), + Gen.choose(-n * 1440L, n * 1440L).map(Duration(_, MINUTES)), + Gen.choose(-n * 86400L, n * 86400L).map(Duration(_, SECONDS)), + Gen.choose(-n * 86400000L, n * 86400000L).map(Duration(_, MILLISECONDS)), + Gen.choose(-n * 86400000000L, n * 86400000000L).map(Duration(_, MICROSECONDS)), + Gen.choose(-n * 86400000000000L, n * 86400000000000L).map(Duration(_, NANOSECONDS)))) + } + // this instance is not available in scalacheck 1.13.2. // remove this once a newer version is available. implicit val cogenBigInt: Cogen[BigInt] = @@ -55,6 +61,37 @@ class LawTests extends FunSuite with Discipline { implicit val cogenUUID: Cogen[UUID] = Cogen[(Long, Long)].contramap(u => (u.getMostSignificantBits, u.getLeastSignificantBits)) + implicit val cogenDuration: Cogen[Duration] = + Cogen[Long].contramap { d => + if (d == Duration.Inf) 3896691548866406746L + else if (d == Duration.MinusInf) 1844151880988859955L + else if (d == Duration.Undefined) -7917359255778781894L + else d.length * (d.unit match { + case DAYS => -6307593037248227856L + case HOURS => -3527447467459552709L + case MINUTES => 5955657079535371609L + case SECONDS => 5314272869665647192L + case MILLISECONDS => -2025740217814855607L + case MICROSECONDS => -2965853209268633779L + case NANOSECONDS => 6128745701389500153L + }) + } +} + +class LawTests extends FunSuite with Discipline { + + import KernelCheck._ + + // The scalacheck defaults (100,100) are too high for scala-js. + final val PropMaxSize: PosZInt = if (Platform.isJs) 10 else 100 + final val PropMinSuccessful: PosInt = if (Platform.isJs) 10 else 100 + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfiguration(minSuccessful = PropMinSuccessful, sizeRange = PropMaxSize) + + implicit def orderLaws[A: Cogen: Eq: Arbitrary]: OrderLaws[A] = OrderLaws[A] + implicit def groupLaws[A: Cogen: Eq: Arbitrary]: GroupLaws[A] = GroupLaws[A] + { // needed for Cogen[Map[...]] implicit val ohe: Ordering[HasEq[Int]] = Ordering[Int].on(_.a) @@ -65,6 +102,7 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Vector[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Stream[HasEq[Int]]].check(_.eqv) + laws[OrderLaws, Queue[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Set[Int]].check(_.partialOrder) laws[OrderLaws, Set[Int]]("reverse").check(_.partialOrder(PartialOrder[Set[Int]].reverse)) @@ -73,6 +111,7 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, List[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, Vector[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, Stream[HasPartialOrder[Int]]].check(_.partialOrder) + laws[OrderLaws, Queue[HasPartialOrder[Int]]].check(_.partialOrder) laws[OrderLaws, Set[Int]]("asMeetPartialOrder").check(_.partialOrder(Semilattice.asMeetPartialOrder[Set[Int]])) laws[OrderLaws, Set[Int]]("asJoinPartialOrder").check(_.partialOrder(Semilattice.asJoinPartialOrder[Set[Int]])) @@ -93,6 +132,7 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, List[String]].check(_.order) laws[OrderLaws, Vector[Int]].check(_.order) laws[OrderLaws, Stream[Int]].check(_.order) + laws[OrderLaws, Queue[Int]].check(_.order) laws[OrderLaws, Int]("fromOrdering").check(_.order(Order.fromOrdering[Int])) laws[OrderLaws, Int]("reverse").check(_.order(Order[Int].reverse)) laws[OrderLaws, Int]("reverse.reverse").check(_.order(Order[Int].reverse.reverse)) @@ -105,6 +145,7 @@ class LawTests extends FunSuite with Discipline { laws[GroupLaws, Stream[Int]].check(_.monoid) laws[GroupLaws, List[String]].check(_.monoid) laws[GroupLaws, Map[String, Int]].check(_.monoid) + laws[GroupLaws, Queue[Int]].check(_.monoid) laws[GroupLaws, BitSet].check(_.boundedSemilattice) laws[GroupLaws, Set[Int]].check(_.boundedSemilattice) @@ -117,6 +158,7 @@ class LawTests extends FunSuite with Discipline { //laws[GroupLaws, Float].check(_.commutativeGroup) // approximately associative //laws[GroupLaws, Double].check(_.commutativeGroup) // approximately associative laws[GroupLaws, BigInt].check(_.commutativeGroup) + laws[GroupLaws, Duration].check(_.commutativeGroup) { // default Arbitrary[BigDecimal] is a bit too intense :/ @@ -273,5 +315,4 @@ class LawTests extends FunSuite with Discipline { private[laws] def laws[L[_] <: Laws, A](extraTag: String)(implicit laws: L[A], tag: TypeTagM[A]): LawChecker[L[A]] = LawChecker("[" + tag.name.toString + (if(extraTag != "") "@@" + extraTag else "") + "]", laws) - } diff --git a/kernel/src/main/scala/cats/kernel/instances/all.scala b/kernel/src/main/scala/cats/kernel/instances/all.scala index 870ea87f8c..269a34d9ff 100644 --- a/kernel/src/main/scala/cats/kernel/instances/all.scala +++ b/kernel/src/main/scala/cats/kernel/instances/all.scala @@ -11,6 +11,7 @@ trait AllInstances with ByteInstances with CharInstances with DoubleInstances + with DurationInstances with FloatInstances with FunctionInstances with IntInstances @@ -18,6 +19,7 @@ trait AllInstances with LongInstances with MapInstances with OptionInstances + with QueueInstances with SetInstances with ShortInstances with StreamInstances diff --git a/kernel/src/main/scala/cats/kernel/instances/duration.scala b/kernel/src/main/scala/cats/kernel/instances/duration.scala new file mode 100644 index 0000000000..a19423101a --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/instances/duration.scala @@ -0,0 +1,32 @@ +package cats.kernel +package instances + +import scala.concurrent.duration.Duration + +package object duration extends DurationInstances + +trait DurationInstances { + implicit val catsKernelStdOrderForDuration: Order[Duration] = new DurationOrder + implicit val catsKernelStdGroupForDuration: CommutativeGroup[Duration] = new DurationGroup +} + +class DurationOrder extends Order[Duration] { + def compare(x: Duration, y: Duration): Int = x compare y + + override def eqv(x: Duration, y: Duration): Boolean = x == y + override def neqv(x: Duration, y: Duration): Boolean = x != y + override def gt(x: Duration, y: Duration): Boolean = x > y + override def gteqv(x: Duration, y: Duration): Boolean = x >= y + override def lt(x: Duration, y: Duration): Boolean = x < y + override def lteqv(x: Duration, y: Duration): Boolean = x <= y + + override def min(x: Duration, y: Duration): Duration = x min y + override def max(x: Duration, y: Duration): Duration = x max y +} + +class DurationGroup extends CommutativeGroup[Duration] { + def empty: Duration = Duration.Zero + def inverse(x: Duration): Duration = -x + def combine(x: Duration, y: Duration): Duration = x + y + override def remove(x: Duration, y: Duration): Duration = x - y +} diff --git a/kernel/src/main/scala/cats/kernel/instances/queue.scala b/kernel/src/main/scala/cats/kernel/instances/queue.scala new file mode 100644 index 0000000000..2018e9f300 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/instances/queue.scala @@ -0,0 +1,52 @@ +package cats.kernel +package instances + +import scala.collection.immutable.Queue + +package object queue extends QueueInstances + +trait QueueInstances extends QueueInstances1 { + implicit def catsKernelStdOrderForQueue[A: Order]: Order[Queue[A]] = + new QueueOrder[A] + implicit def catsKernelStdMonoidForQueue[A]: Monoid[Queue[A]] = + new QueueMonoid[A] +} + +trait QueueInstances1 extends QueueInstances2 { + implicit def catsKernelStdPartialOrderForQueue[A: PartialOrder]: PartialOrder[Queue[A]] = + new QueuePartialOrder[A] +} + +trait QueueInstances2 { + implicit def catsKernelStdEqForQueue[A: Eq]: Eq[Queue[A]] = + new QueueEq[A] +} + +class QueueOrder[A](implicit ev: Order[A]) extends Order[Queue[A]] { + def compare(xs: Queue[A], ys: Queue[A]): Int = + if (xs eq ys) 0 + else StaticMethods.iteratorCompare(xs.iterator, ys.iterator) +} + +class QueuePartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Queue[A]] { + def partialCompare(xs: Queue[A], ys: Queue[A]): Double = + if (xs eq ys) 0.0 + else StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) +} + +class QueueEq[A](implicit ev: Eq[A]) extends Eq[Queue[A]] { + def eqv(xs: Queue[A], ys: Queue[A]): Boolean = + if (xs eq ys) true + else StaticMethods.iteratorEq(xs.iterator, ys.iterator) +} + +class QueueMonoid[A] extends Monoid[Queue[A]] { + def empty: Queue[A] = Queue.empty[A] + def combine(x: Queue[A], y: Queue[A]): Queue[A] = x ++ y + + override def combineN(x: Queue[A], n: Int): Queue[A] = + StaticMethods.combineNIterable(Queue.newBuilder[A], x, n) + + override def combineAll(xs: TraversableOnce[Queue[A]]): Queue[A] = + StaticMethods.combineAllIterable(Queue.newBuilder[A], xs) +} diff --git a/tests/src/test/scala/cats/tests/QueueTests.scala b/tests/src/test/scala/cats/tests/QueueTests.scala new file mode 100644 index 0000000000..0406d48e84 --- /dev/null +++ b/tests/src/test/scala/cats/tests/QueueTests.scala @@ -0,0 +1,28 @@ +package cats +package tests + +import scala.collection.immutable.Queue + +import cats.laws.discipline.{CoflatMapTests, MonadTests, AlternativeTests, SerializableTests, TraverseTests, CartesianTests} + +class QueueTests extends CatsSuite { + checkAll("Queue[Int]", CartesianTests[Queue].cartesian[Int, Int, Int]) + checkAll("Cartesian[Queue]", SerializableTests.serializable(Cartesian[Queue])) + + checkAll("Queue[Int]", CoflatMapTests[Queue].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[Queue]", SerializableTests.serializable(CoflatMap[Queue])) + + checkAll("Queue[Int]", AlternativeTests[Queue].alternative[Int, Int, Int]) + checkAll("Alternative[Queue]", SerializableTests.serializable(Alternative[Queue])) + + checkAll("Queue[Int]", MonadTests[Queue].monad[Int, Int, Int]) + checkAll("Monad[Queue]", SerializableTests.serializable(Monad[Queue])) + + checkAll("Queue[Int] with Option", TraverseTests[Queue].traverse[Int, Int, Int, List[Int], Option, Option]) + checkAll("Traverse[Queue]", SerializableTests.serializable(Traverse[Queue])) + + test("show") { + Queue(1, 2, 3).show should === ("Queue(1, 2, 3)") + Queue.empty[Int].show should === ("Queue()") + } +} From ab446616e7742f4b0409cea1d7adb316f121eeab Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 31 Aug 2017 10:35:22 -0400 Subject: [PATCH 2/9] Maintain kernel bincompat. --- kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala | 4 ++++ kernel/src/main/scala/cats/kernel/instances/all.scala | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index 2a6a854176..e00de06985 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -6,6 +6,10 @@ import catalysts.macros.TypeTagM import cats.kernel.instances.all._ +// these aren't included in all due to bincompat +import cats.kernel.instances.duration._ +import cats.kernel.instances.queue._ + import org.typelevel.discipline.{ Laws } import org.typelevel.discipline.scalatest.Discipline import org.scalacheck.{ Arbitrary, Cogen, Gen } diff --git a/kernel/src/main/scala/cats/kernel/instances/all.scala b/kernel/src/main/scala/cats/kernel/instances/all.scala index 269a34d9ff..9fe1093f5f 100644 --- a/kernel/src/main/scala/cats/kernel/instances/all.scala +++ b/kernel/src/main/scala/cats/kernel/instances/all.scala @@ -11,7 +11,7 @@ trait AllInstances with ByteInstances with CharInstances with DoubleInstances - with DurationInstances + //with DurationInstances // left out for bincompat with FloatInstances with FunctionInstances with IntInstances @@ -19,7 +19,7 @@ trait AllInstances with LongInstances with MapInstances with OptionInstances - with QueueInstances + //with QueueInstances // left out for bincompat with SetInstances with ShortInstances with StreamInstances From e98b09f40a2404b9a14437ad765247aabd93414d Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 31 Aug 2017 11:03:50 -0400 Subject: [PATCH 3/9] Fix formatting. --- kernel/src/main/scala/cats/kernel/instances/all.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/instances/all.scala b/kernel/src/main/scala/cats/kernel/instances/all.scala index 9fe1093f5f..6ac3f0fb84 100644 --- a/kernel/src/main/scala/cats/kernel/instances/all.scala +++ b/kernel/src/main/scala/cats/kernel/instances/all.scala @@ -11,7 +11,7 @@ trait AllInstances with ByteInstances with CharInstances with DoubleInstances - //with DurationInstances // left out for bincompat + // with DurationInstances // left out for bincompat with FloatInstances with FunctionInstances with IntInstances @@ -19,7 +19,7 @@ trait AllInstances with LongInstances with MapInstances with OptionInstances - //with QueueInstances // left out for bincompat + // with QueueInstances // left out for bincompat with SetInstances with ShortInstances with StreamInstances From 75031baf3cc6ea59e29fb139f1c34709e1e1a7b5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 1 Sep 2017 02:54:02 -0400 Subject: [PATCH 4/9] Add a bunch of tests. --- core/src/main/scala/cats/instances/eq.scala | 12 ++-- .../src/main/scala/cats/instances/order.scala | 14 ++-- .../main/scala/cats/instances/ordering.scala | 14 ++-- .../scala/cats/instances/partialOrder.scala | 14 ++-- .../cats/instances/partialOrdering.scala | 17 +++-- .../scala/cats/kernel/laws/LawTests.scala | 1 + .../cats/kernel/instances/duration.scala | 15 ++++ .../main/scala/cats/laws/FoldableLaws.scala | 35 +++++++++ .../main/scala/cats/laws/TraverseLaws.scala | 22 +++++- .../cats/laws/discipline/Arbitrary.scala | 33 ++++++--- .../main/scala/cats/laws/discipline/Eq.scala | 71 ++++++++----------- .../cats/laws/discipline/FoldableTests.scala | 8 ++- .../cats/laws/discipline/TraverseTests.scala | 9 ++- .../test/scala/cats/tests/EquivTests.scala | 19 +++++ .../cats/tests/KernelContravariantTests.scala | 19 ++++- .../test/scala/cats/tests/OrderingTests.scala | 19 +++++ .../cats/tests/PartialOrderingTests.scala | 19 +++++ 17 files changed, 262 insertions(+), 79 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/EquivTests.scala create mode 100644 tests/src/test/scala/cats/tests/OrderingTests.scala create mode 100644 tests/src/test/scala/cats/tests/PartialOrderingTests.scala diff --git a/core/src/main/scala/cats/instances/eq.scala b/core/src/main/scala/cats/instances/eq.scala index aab0f91579..6daf449e56 100644 --- a/core/src/main/scala/cats/instances/eq.scala +++ b/core/src/main/scala/cats/instances/eq.scala @@ -2,9 +2,11 @@ package cats package instances trait EqInstances { - implicit val catsContravariantCartesianEq: ContravariantCartesian[Eq] = new ContravariantCartesian[Eq] { - def contramap[A, B](fa: Eq[A])(fn: B => A): Eq[B] = fa.on(fn) - def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] = - Eq.instance { (left, right) => fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2) } - } + implicit val catsContravariantCartesianForEq: ContravariantCartesian[Eq] = + new ContravariantCartesian[Eq] { + def contramap[A, B](fa: Eq[A])(fn: B => A): Eq[B] = + fa.on(fn) + def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] = + Eq.instance { (left, right) => fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2) } + } } diff --git a/core/src/main/scala/cats/instances/order.scala b/core/src/main/scala/cats/instances/order.scala index 8dba09b790..29fad0826d 100644 --- a/core/src/main/scala/cats/instances/order.scala +++ b/core/src/main/scala/cats/instances/order.scala @@ -1,17 +1,23 @@ package cats package instances -import cats.functor.Contravariant - trait OrderInstances extends cats.kernel.OrderToOrderingConversion { - implicit val catsFunctorContravariantForOrder: Contravariant[Order] = - new Contravariant[Order] { + implicit val catsContravariantCartesianForOrder: ContravariantCartesian[Order] = + new ContravariantCartesian[Order] { /** Derive an `Order` for `B` given an `Order[A]` and a function `B => A`. * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ def contramap[A, B](fa: Order[A])(f: B => A): Order[B] = fa.on(f) + + def product[A, B](fa: Order[A], fb: Order[B]): Order[(A, B)] = + new Order[(A, B)] { + def compare(x: (A, B), y: (A, B)): Int = { + val z = fa.compare(x._1, y._1) + if (z == 0) fb.compare(x._2, y._2) else z + } + } } } diff --git a/core/src/main/scala/cats/instances/ordering.scala b/core/src/main/scala/cats/instances/ordering.scala index 1ef599e127..cb8ea6e5a1 100644 --- a/core/src/main/scala/cats/instances/ordering.scala +++ b/core/src/main/scala/cats/instances/ordering.scala @@ -1,16 +1,22 @@ package cats package instances -import cats.functor.Contravariant - trait OrderingInstances { - implicit val catsFunctorContravariantForOrdering: Contravariant[Ordering] = - new Contravariant[Ordering] { + implicit val catsContravariantCartesianForOrdering: ContravariantCartesian[Ordering] = + new ContravariantCartesian[Ordering] { /** Derive an `Ordering` for `B` given an `Ordering[A]` and a function `B => A`. * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ def contramap[A, B](fa: Ordering[A])(f: B => A): Ordering[B] = fa.on(f) + + def product[A, B](fa: Ordering[A], fb: Ordering[B]): Ordering[(A, B)] = + new Ordering[(A, B)] { + def compare(x: (A, B), y: (A, B)): Int = { + val z = fa.compare(x._1, y._1) + if (z == 0) fb.compare(x._2, y._2) else z + } + } } } diff --git a/core/src/main/scala/cats/instances/partialOrder.scala b/core/src/main/scala/cats/instances/partialOrder.scala index 37e6db2146..a8b4654e2e 100644 --- a/core/src/main/scala/cats/instances/partialOrder.scala +++ b/core/src/main/scala/cats/instances/partialOrder.scala @@ -1,15 +1,21 @@ package cats package instances -import cats.functor.Contravariant - trait PartialOrderInstances { - implicit val catsFunctorContravariantForPartialOrder: Contravariant[PartialOrder] = - new Contravariant[PartialOrder] { + implicit val catsContravariantCartesianForPartialOrder: ContravariantCartesian[PartialOrder] = + new ContravariantCartesian[PartialOrder] { /** Derive a `PartialOrder` for `B` given a `PartialOrder[A]` and a function `B => A`. * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) */ def contramap[A, B](fa: PartialOrder[A])(f: B => A): PartialOrder[B] = fa.on(f) + + def product[A, B](fa: PartialOrder[A], fb: PartialOrder[B]): PartialOrder[(A, B)] = + new PartialOrder[(A, B)] { + def partialCompare(x: (A, B), y: (A, B)): Double = { + val z = fa.partialCompare(x._1, y._1) + if (z == 0.0) fb.partialCompare(x._2, y._2) else z + } + } } } diff --git a/core/src/main/scala/cats/instances/partialOrdering.scala b/core/src/main/scala/cats/instances/partialOrdering.scala index 05f7174720..5b02dc384a 100644 --- a/core/src/main/scala/cats/instances/partialOrdering.scala +++ b/core/src/main/scala/cats/instances/partialOrdering.scala @@ -1,11 +1,9 @@ package cats package instances -import cats.functor.Contravariant - trait PartialOrderingInstances { - implicit val catsFunctorContravariantForPartialOrdering: Contravariant[PartialOrdering] = - new Contravariant[PartialOrdering] { + implicit val catsContravariantCartesianForPartialOrdering: ContravariantCartesian[PartialOrdering] = + new ContravariantCartesian[PartialOrdering] { /** Derive a `PartialOrdering` for `B` given a `PartialOrdering[A]` and a function `B => A`. * * Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping) @@ -15,5 +13,16 @@ trait PartialOrderingInstances { def lteq(x: B, y: B): Boolean = fa.lteq(f(x), f(y)) def tryCompare(x: B, y: B): Option[Int] = fa.tryCompare(f(x), f(y)) } + + def product[A, B](fa: PartialOrdering[A], fb: PartialOrdering[B]): PartialOrdering[(A, B)] = + new PartialOrdering[(A, B)] { + def lteq(x: (A, B), y: (A, B)): Boolean = + tryCompare(x, y).exists(_ <= 0) + def tryCompare(x: (A, B), y: (A, B)): Option[Int] = + fa.tryCompare(x._1, y._1) match { + case Some(0) => fb.tryCompare(x._2, y._2) + case option => option + } + } } } diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index e00de06985..bd052d2014 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -130,6 +130,7 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, Long].check(_.order) laws[OrderLaws, BitSet].check(_.partialOrder) laws[OrderLaws, BigInt].check(_.order) + laws[OrderLaws, Duration].check(_.order) laws[OrderLaws, UUID].check(_.order) laws[OrderLaws, List[Int]].check(_.order) laws[OrderLaws, Option[String]].check(_.order) diff --git a/kernel/src/main/scala/cats/kernel/instances/duration.scala b/kernel/src/main/scala/cats/kernel/instances/duration.scala index a19423101a..fbbfca600e 100644 --- a/kernel/src/main/scala/cats/kernel/instances/duration.scala +++ b/kernel/src/main/scala/cats/kernel/instances/duration.scala @@ -10,6 +10,14 @@ trait DurationInstances { implicit val catsKernelStdGroupForDuration: CommutativeGroup[Duration] = new DurationGroup } +// Duration.Undefined, Duration.Inf, Duration.MinusInf + +/** + * This ordering is valid for all defined durations. + * + * The value Duration.Undefined breaks our laws, because undefined + * values are not equal to themselves. + */ class DurationOrder extends Order[Duration] { def compare(x: Duration, y: Duration): Int = x compare y @@ -24,6 +32,13 @@ class DurationOrder extends Order[Duration] { override def max(x: Duration, y: Duration): Duration = x max y } +/** + * This group models addition, but has a few problematic edge cases. + * + * 1. finite values can overflow, throwing an exception + * 2. inf + (-inf) = undefined, not zero + * 3. undefined + zero = undefined + */ class DurationGroup extends CommutativeGroup[Duration] { def empty: Duration = Duration.Zero def inverse(x: Duration): Duration = -x diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 6fc378095b..1e36678bcb 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -3,6 +3,8 @@ package laws import cats.implicits._ +import scala.collection.mutable + trait FoldableLaws[F[_]] { implicit def F: Foldable[F] @@ -106,6 +108,39 @@ trait FoldableLaws[F[_]] { val g: (A, Eval[A]) => Eval[A] = (a, ea) => ea.map(f(a, _)) F.reduceRightOption(fa)(g).value <-> F.reduceRightToOption(fa)(identity)(g).value } + + def getRef[A](fa: F[A], idx: Long): IsEq[Option[A]] = + F.get(fa)(idx) <-> ( + if (idx < 0L) None + else F.foldM[Either[A, ?], A, Long](fa, 0L) { (i, a) => + if (i == idx) Left(a) else Right(i + 1L) + } match { + case Left(a) => Some(a) + case Right(_) => None + }) + + def foldRef[A](fa: F[A])(implicit A: Monoid[A]): IsEq[A] = + F.fold(fa) <-> F.foldLeft(fa, A.empty) { (acc, a) => A.combine(acc, a) } + + def toListRef[A](fa: F[A]): IsEq[List[A]] = + F.toList(fa) <-> F.foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => + buf += a + }.toList + + def filter_Ref[A](fa: F[A], p: A => Boolean): IsEq[List[A]] = + F.filter_(fa)(p) <-> F.foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => + if (p(a)) buf += a else buf + }.toList + + def takeWhile_Ref[A](fa: F[A], p: A => Boolean): IsEq[List[A]] = + F.takeWhile_(fa)(p) <-> F.foldRight(fa, Now(List.empty[A])) { (a, llst) => + if (p(a)) llst.map(a :: _) else Now(Nil) + }.value + + def dropWhile_[A](fa: F[A], p: A => Boolean): IsEq[List[A]] = + F.dropWhile_(fa)(p) <-> F.foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => + if (buf.nonEmpty || !p(a)) buf += a else buf + }.toList } object FoldableLaws { diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index add5bb70b1..e7929fe4bf 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -2,7 +2,7 @@ package cats package laws import cats.Id -import cats.data.{Const, Nested} +import cats.data.{Const, Nested, State, StateT} import cats.syntax.traverse._ import cats.syntax.foldable._ @@ -66,6 +66,26 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { val rhs: B = fa.foldMap(f) lhs <-> rhs } + + def mapWithIndexRef[A, B](fa: F[A], f: (A, Int) => B): IsEq[F[B]] = { + val lhs = F.mapWithIndex(fa)(f) + val rhs = F.traverse(fa)(a => + State((s: Int) => (s + 1, f(a, s)))).runA(0).value + lhs <-> rhs + } + + def traverseWithIndexMRef[G[_], A, B](fa: F[A], f: (A, Int) => G[B])(implicit G: Monad[G]): IsEq[G[F[B]]] = { + val lhs = F.traverseWithIndexM(fa)(f) + val rhs = F.traverse(fa)(a => + StateT((s: Int) => G.map(f(a, s))(b => (s + 1, b)))).runA(0) + lhs <-> rhs + } + + def zipWithIndexRef[A, B](fa: F[A], f: ((A, Int)) => B): IsEq[F[B]] = { + val lhs = F.map(F.zipWithIndex(fa))(f) + val rhs = F.map(F.mapWithIndex(fa)((a, i) => (a, i)))(f) + lhs <-> rhs + } } object TraverseLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 963d71faa2..d309b12aa9 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -133,21 +133,34 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForFn0[A: Arbitrary]: Arbitrary[() => A] = Arbitrary(getArbitrary[A].map(() => _)) + // TODO: we should probably be using Cogen for generating Eq, Order, + // etc. however, we'd still have to ensure that (x.## == y.##) + // implies equal, in order to avoid producing invalid instances. + implicit def catsLawsArbitraryForEq[A: Arbitrary]: Arbitrary[Eq[A]] = - Arbitrary(new Eq[A] { def eqv(x: A, y: A) = x.hashCode == y.hashCode }) + Arbitrary(getArbitrary[Int => Int].map(f => new Eq[A] { + def eqv(x: A, y: A): Boolean = f(x.##) == f(y.##) + })) + + implicit def catsLawsArbitraryForEquiv[A: Arbitrary]: Arbitrary[Equiv[A]] = + Arbitrary(getArbitrary[Eq[A]].map(Eq.catsKernelEquivForEq(_))) implicit def catsLawsArbitraryForPartialOrder[A: Arbitrary]: Arbitrary[PartialOrder[A]] = - Arbitrary(Gen.oneOf( - PartialOrder.from[A]((_: A, _: A) => Double.NaN), - PartialOrder.from[A]((_: A, _: A) => -1.0), - PartialOrder.from[A]((_: A, _: A) => 0.0), - PartialOrder.from[A]((_: A, _: A) => 1.0))) + Arbitrary(getArbitrary[Int => Double].map(f => new PartialOrder[A] { + def partialCompare(x: A, y: A): Double = + if (x.## == y.##) 0.0 else f(x.##) - f(y.##) + })) + + implicit def catsLawsArbitraryForPartialOrdering[A: Arbitrary]: Arbitrary[PartialOrdering[A]] = + Arbitrary(getArbitrary[PartialOrder[A]].map(PartialOrder.catsKernelPartialOrderingForPartialOrder(_))) implicit def catsLawsArbitraryForOrder[A: Arbitrary]: Arbitrary[Order[A]] = - Arbitrary(Gen.oneOf( - Order.from[A]((_: A, _: A) => -1), - Order.from[A]((_: A, _: A) => 0), - Order.from[A]((_: A, _: A) => 1))) + Arbitrary(getArbitrary[Int => Int].map(f => new Order[A] { + def compare(x: A, y: A): Int = f(x.##) compare f(y.##) + })) + + implicit def catsLawsArbitraryForOrdering[A: Arbitrary]: Arbitrary[Ordering[A]] = + Arbitrary(getArbitrary[Order[A]].map(Order.catsKernelOrderingForOrder(_))) implicit def catsLawsArbitraryForNested[F[_], G[_], A](implicit FG: Arbitrary[F[G[A]]]): Arbitrary[Nested[F, G, A]] = Arbitrary(FG.arbitrary.map(Nested(_))) diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index a7f4541fee..8186c84743 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -3,7 +3,10 @@ package laws package discipline import catalysts.Platform +import cats.instances.boolean._ +import cats.instances.int._ import cats.instances.string._ +import cats.syntax.eq._ import org.scalacheck.Arbitrary object eq { @@ -48,52 +51,36 @@ object eq { } /** - * Create an approximation of Eq[Eq[A]] by generating 100 values for A - * and comparing the application of the two eqv functions + * Create an approximate Eq instance for some type A, by comparing + * the behavior of `f(x, b)` and `f(y, b)` across many `b` samples. */ - implicit def catsLawsEqForEq[A](implicit arbA: Arbitrary[(A, A)]): Eq[Eq[A]] = new Eq[Eq[A]] { - def eqv(f: Eq[A], g: Eq[A]): Boolean = { - val samples = List.fill(100)(arbA.arbitrary.sample).collect { - case Some(a) => a - case None => sys.error("Could not generate arbitrary values to compare two Eq[A]") - } - samples.forall { - case (l, r) => f.eqv(l, r) == g.eqv(l, r) - } + def sampledEq[A, B: Arbitrary, C: Eq](samples: Int)(f: (A, B) => C): Eq[A] = + new Eq[A] { + val gen = Arbitrary.arbitrary[B] + def eqv(x: A, y: A): Boolean = + Iterator.range(1, samples) + .map(_ => gen.sample) + .map(_.getOrElse(sys.error(s"generator $gen failed"))) + .forall { b => f(x, b) === f(y, b) } } - } - /** - * Create an approximation of Eq[PartialOrder[A]] by generating 100 values for A - * and comparing the application of the two compare functions - */ - implicit def catsLawsEqForPartialOrder[A](implicit arbA: Arbitrary[(A, A)], optIntEq: Eq[Option[Int]]): Eq[PartialOrder[A]] = new Eq[PartialOrder[A]] { - def eqv(f: PartialOrder[A], g: PartialOrder[A]): Boolean = { - val samples = List.fill(100)(arbA.arbitrary.sample).collect { - case Some(a) => a - case None => sys.error("Could not generate arbitrary values to compare two PartialOrder[A]") - } - samples.forall { - case (l, r) => optIntEq.eqv(f.tryCompare(l, r), g.tryCompare(l, r)) - } - } - } + implicit def catsLawsEqForEq[A](implicit arbA: Arbitrary[(A, A)]): Eq[Eq[A]] = + sampledEq[Eq[A], (A, A), Boolean](100) { case (e, (l, r)) => e.eqv(l, r) } - /** - * Create an approximation of Eq[Order[A]] by generating 100 values for A - * and comparing the application of the two compare functions - */ - implicit def catsLawsEqForOrder[A](implicit arbA: Arbitrary[(A, A)]): Eq[Order[A]] = new Eq[Order[A]] { - def eqv(f: Order[A], g: Order[A]): Boolean = { - val samples = List.fill(100)(arbA.arbitrary.sample).collect { - case Some(a) => a - case None => sys.error("Could not generate arbitrary values to compare two Order[A]") - } - samples.forall { - case (l, r) => f.compare(l, r) == g.compare(l, r) - } - } - } + implicit def catsLawsEqForEquiv[A](implicit arbA: Arbitrary[(A, A)]): Eq[Equiv[A]] = + sampledEq[Equiv[A], (A, A), Boolean](100) { case (e, (l, r)) => e.equiv(l, r) } + + implicit def catsLawsEqForPartialOrder[A](implicit arbA: Arbitrary[(A, A)], optIntEq: Eq[Option[Int]]): Eq[PartialOrder[A]] = + sampledEq[PartialOrder[A], (A, A), Option[Int]](100) { case (p, (l, r)) => p.tryCompare(l, r) } + + implicit def catsLawsEqForPartialOrdering[A](implicit arbA: Arbitrary[(A, A)], optIntEq: Eq[Option[Int]]): Eq[PartialOrdering[A]] = + sampledEq[PartialOrdering[A], (A, A), Option[Int]](100) { case (p, (l, r)) => p.tryCompare(l, r) } + + implicit def catsLawsEqForOrder[A](implicit arbA: Arbitrary[(A, A)]): Eq[Order[A]] = + sampledEq[Order[A], (A, A), Int](100) { case (p, (l, r)) => p.compare(l, r) } + + implicit def catsLawsEqForOrdering[A](implicit arbA: Arbitrary[(A, A)]): Eq[Ordering[A]] = + sampledEq[Ordering[A], (A, A), Int](100) { case (p, (l, r)) => p.compare(l, r) } /** * Create an approximation of Eq[Semigroup[A]] by generating values for A diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 8538d0bc99..290f0c3e85 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -31,7 +31,13 @@ trait FoldableTests[F[_]] extends Laws { "reduceLeftOption consistent with reduceLeftToOption" -> forAll(laws.reduceLeftOptionConsistentWithReduceLeftToOption[A] _), "reduceRightOption consistent with reduceRightToOption" -> - forAll(laws.reduceRightOptionConsistentWithReduceRightToOption[A] _) + forAll(laws.reduceRightOptionConsistentWithReduceRightToOption[A] _), + "get reference" -> forAll(laws.getRef[A] _), + "fold reference" -> forAll(laws.getRef[A] _), + "toList reference" -> forAll(laws.getRef[A] _), + "filter_ reference" -> forAll(laws.getRef[A] _), + "takeWhile_ reference" -> forAll(laws.getRef[A] _), + "dropWhile_ reference" -> forAll(laws.getRef[A] _), ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index 7164707dc9..dee5ea7ed9 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -2,13 +2,15 @@ package cats package laws package discipline +import cats.instances.list._ + import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ - trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { def laws: TraverseLaws[F] + def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_]: Applicative, Y[_]: Applicative](implicit ArbFA: Arbitrary[F[A]], ArbXB: Arbitrary[X[B]], @@ -39,7 +41,10 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { "traverse identity" -> forAll(laws.traverseIdentity[A, C] _), "traverse sequential composition" -> forAll(laws.traverseSequentialComposition[A, B, C, X, Y] _), "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _), - "traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _) + "traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _), + "traverse ref mapWithIndex" -> forAll(laws.mapWithIndexRef[A, C] _), + "traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[List, A, C] _), + "traverse ref zipWithIndex" -> forAll(laws.zipWithIndexRef[A, C] _) ) } } diff --git a/tests/src/test/scala/cats/tests/EquivTests.scala b/tests/src/test/scala/cats/tests/EquivTests.scala new file mode 100644 index 0000000000..1dfb232baf --- /dev/null +++ b/tests/src/test/scala/cats/tests/EquivTests.scala @@ -0,0 +1,19 @@ +package cats +package tests + +import cats.functor._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline._ +import cats.laws.discipline.eq._ + +class EquivTests extends CatsSuite { + + Invariant[Equiv] + Contravariant[Equiv] + Cartesian[Equiv] + ContravariantCartesian[Equiv] + + checkAll("Contravariant[Equiv]", ContravariantTests[Equiv].contravariant[Int, Int, Int]) + checkAll("Cartesian[Equiv]", CartesianTests[Equiv].cartesian[Int, Int, Int]) + checkAll("Contravariant[Equiv]", SerializableTests.serializable(Contravariant[Equiv])) +} diff --git a/tests/src/test/scala/cats/tests/KernelContravariantTests.scala b/tests/src/test/scala/cats/tests/KernelContravariantTests.scala index 67297510b0..d4a4c67914 100644 --- a/tests/src/test/scala/cats/tests/KernelContravariantTests.scala +++ b/tests/src/test/scala/cats/tests/KernelContravariantTests.scala @@ -1,18 +1,33 @@ package cats package tests -import cats.functor.Contravariant +import cats.functor._ import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{ContravariantTests, SerializableTests} +import cats.laws.discipline._ import cats.laws.discipline.eq._ class KernelContravariantTests extends CatsSuite { + Invariant[Eq] + Contravariant[Eq] + Cartesian[Eq] + ContravariantCartesian[Eq] checkAll("Contravariant[Eq]", ContravariantTests[Eq].contravariant[Int, Int, Int]) + checkAll("Cartesian[Eq]", CartesianTests[Eq].cartesian[Int, Int, Int]) checkAll("Contravariant[Eq]", SerializableTests.serializable(Contravariant[Eq])) + Invariant[PartialOrder] + Contravariant[PartialOrder] + Cartesian[PartialOrder] + ContravariantCartesian[PartialOrder] checkAll("Contravariant[PartialOrder]", ContravariantTests[PartialOrder].contravariant[Int, Int, Int]) + checkAll("Cartesian[PartialOrder]", CartesianTests[PartialOrder].cartesian[Int, Int, Int]) checkAll("Contravariant[PartialOrder]", SerializableTests.serializable(Contravariant[PartialOrder])) + Invariant[Order] + Contravariant[Order] + Cartesian[Order] + ContravariantCartesian[Order] checkAll("Contravariant[Order]", ContravariantTests[Order].contravariant[Int, Int, Int]) + checkAll("Cartesian[Order]", CartesianTests[Order].cartesian[Int, Int, Int]) checkAll("Contravariant[Order]", SerializableTests.serializable(Contravariant[Order])) } diff --git a/tests/src/test/scala/cats/tests/OrderingTests.scala b/tests/src/test/scala/cats/tests/OrderingTests.scala new file mode 100644 index 0000000000..f629856c5e --- /dev/null +++ b/tests/src/test/scala/cats/tests/OrderingTests.scala @@ -0,0 +1,19 @@ +package cats +package tests + +import cats.functor._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline._ +import cats.laws.discipline.eq._ + +class OrderingTests extends CatsSuite { + + Invariant[Ordering] + Contravariant[Ordering] + Cartesian[Ordering] + ContravariantCartesian[Ordering] + + checkAll("Contravariant[Ordering]", ContravariantTests[Ordering].contravariant[Int, Int, Int]) + checkAll("Cartesian[Ordering]", CartesianTests[Ordering].cartesian[Int, Int, Int]) + checkAll("Contravariant[Ordering]", SerializableTests.serializable(Contravariant[Ordering])) +} diff --git a/tests/src/test/scala/cats/tests/PartialOrderingTests.scala b/tests/src/test/scala/cats/tests/PartialOrderingTests.scala new file mode 100644 index 0000000000..4825f79854 --- /dev/null +++ b/tests/src/test/scala/cats/tests/PartialOrderingTests.scala @@ -0,0 +1,19 @@ +package cats +package tests + +import cats.functor._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline._ +import cats.laws.discipline.eq._ + +class PartialOrderingTests extends CatsSuite { + + Invariant[PartialOrdering] + Contravariant[PartialOrdering] + Cartesian[PartialOrdering] + ContravariantCartesian[PartialOrdering] + + checkAll("Contravariant[PartialOrdering]", ContravariantTests[PartialOrdering].contravariant[Int, Int, Int]) + checkAll("Cartesian[PartialOrdering]", CartesianTests[PartialOrdering].cartesian[Int, Int, Int]) + checkAll("Contravariant[PartialOrdering]", SerializableTests.serializable(Contravariant[PartialOrdering])) +} From 06ca15bdb5e4605419068ae743d33682f613c4d8 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 1 Sep 2017 03:02:15 -0400 Subject: [PATCH 5/9] Trailing comma, lol. --- laws/src/main/scala/cats/laws/discipline/FoldableTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 290f0c3e85..659f289edd 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -37,7 +37,7 @@ trait FoldableTests[F[_]] extends Laws { "toList reference" -> forAll(laws.getRef[A] _), "filter_ reference" -> forAll(laws.getRef[A] _), "takeWhile_ reference" -> forAll(laws.getRef[A] _), - "dropWhile_ reference" -> forAll(laws.getRef[A] _), + "dropWhile_ reference" -> forAll(laws.getRef[A] _) ) } } From 746357ed79bcddf7ae5fba74664652bbb1879470 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 1 Sep 2017 13:02:04 -0400 Subject: [PATCH 6/9] Get tests running under 2.10, and fix some bugs. --- core/src/main/scala/cats/data/NonEmptyVector.scala | 2 +- core/src/main/scala/cats/instances/map.scala | 12 +++++++----- .../main/scala/cats/laws/discipline/Arbitrary.scala | 2 +- .../scala/cats/laws/discipline/TraverseTests.scala | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 5b32ca145a..6ca7595c4b 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -244,7 +244,7 @@ private[data] sealed trait NonEmptyVectorInstances { fa.foldRight(lb)(f) override def get[A](fa: NonEmptyVector[A])(idx: Long): Option[A] = - if (idx < Int.MaxValue) fa.get(idx.toInt) else None + if (0 <= idx && idx < Int.MaxValue) fa.get(idx.toInt) else None def tailRecM[A, B](a: A)(f: A => NonEmptyVector[Either[A, B]]): NonEmptyVector[B] = { val buf = new VectorBuilder[B] diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index e9afc6fd84..c364f6d60f 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -74,11 +74,13 @@ trait MapInstances extends cats.kernel.instances.MapInstances { override def size[A](fa: Map[K, A]): Long = fa.size.toLong - override def get[A](fa: Map[K, A])(idx: Long): Option[A] = { - if (idx >= 0L && idx < fa.size && idx < Int.MaxValue) - Some(fa.valuesIterator.drop(idx.toInt - 1).next) - else None - } + override def get[A](fa: Map[K, A])(idx: Long): Option[A] = + if (idx < 0L || Int.MaxValue < idx) None + else { + val n = idx.toInt + if (n >= fa.size) None + else Some(fa.valuesIterator.drop(n).next) + } override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index d309b12aa9..12f5432459 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -156,7 +156,7 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForOrder[A: Arbitrary]: Arbitrary[Order[A]] = Arbitrary(getArbitrary[Int => Int].map(f => new Order[A] { - def compare(x: A, y: A): Int = f(x.##) compare f(y.##) + def compare(x: A, y: A): Int = java.lang.Integer.compare(f(x.##), f(y.##)) })) implicit def catsLawsArbitraryForOrdering[A: Arbitrary]: Arbitrary[Ordering[A]] = diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index dee5ea7ed9..00dffff068 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -43,7 +43,7 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _), "traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _), "traverse ref mapWithIndex" -> forAll(laws.mapWithIndexRef[A, C] _), - "traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[List, A, C] _), + //"traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[List, A, C] _), "traverse ref zipWithIndex" -> forAll(laws.zipWithIndexRef[A, C] _) ) } From 0d5ed9ede4e0fe41907d688822df6c77aa1d3c43 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 1 Sep 2017 13:31:53 -0400 Subject: [PATCH 7/9] Appease styler :/ --- laws/src/main/scala/cats/laws/discipline/TraverseTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index 00dffff068..c04d612507 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -43,7 +43,7 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _), "traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _), "traverse ref mapWithIndex" -> forAll(laws.mapWithIndexRef[A, C] _), - //"traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[List, A, C] _), + // "traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[List, A, C] _), "traverse ref zipWithIndex" -> forAll(laws.zipWithIndexRef[A, C] _) ) } From 3453cd141f01129510d3a0774ae01ecf831892e4 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 1 Sep 2017 13:39:00 -0400 Subject: [PATCH 8/9] Reenable lighterweight test. --- laws/src/main/scala/cats/laws/discipline/TraverseTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index c04d612507..2ddba1c471 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.instances.list._ +import cats.instances.option._ import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ @@ -43,7 +43,7 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _), "traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _), "traverse ref mapWithIndex" -> forAll(laws.mapWithIndexRef[A, C] _), - // "traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[List, A, C] _), + "traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[Option, A, C] _), "traverse ref zipWithIndex" -> forAll(laws.zipWithIndexRef[A, C] _) ) } From adcb06be7e8dd252d3e4eefd725a25b83d28b64e Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 1 Sep 2017 14:48:08 -0400 Subject: [PATCH 9/9] Enable more reference tests. --- laws/src/main/scala/cats/laws/FoldableLaws.scala | 2 +- .../scala/cats/laws/discipline/FoldableTests.scala | 14 +++++++++----- .../laws/discipline/NonEmptyTraverseTests.scala | 1 + .../cats/laws/discipline/ReducibleTests.scala | 1 + .../scala/cats/laws/discipline/TraverseTests.scala | 2 ++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 1e36678bcb..8adc63040b 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -137,7 +137,7 @@ trait FoldableLaws[F[_]] { if (p(a)) llst.map(a :: _) else Now(Nil) }.value - def dropWhile_[A](fa: F[A], p: A => Boolean): IsEq[List[A]] = + def dropWhile_Ref[A](fa: F[A], p: A => Boolean): IsEq[List[A]] = F.dropWhile_(fa)(p) <-> F.foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => if (buf.nonEmpty || !p(a)) buf += a else buf }.toList diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 659f289edd..d7a3868bec 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -6,14 +6,18 @@ import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Prop._ import org.typelevel.discipline.Laws +import cats.instances.list._ + trait FoldableTests[F[_]] extends Laws { def laws: FoldableLaws[F] def foldable[A: Arbitrary, B: Arbitrary](implicit ArbFA: Arbitrary[F[A]], + A: Monoid[A], B: Monoid[B], CogenA: Cogen[A], CogenB: Cogen[B], + EqA: Eq[A], EqB: Eq[B], EqOptionA: Eq[Option[A]] ): RuleSet = { @@ -33,11 +37,11 @@ trait FoldableTests[F[_]] extends Laws { "reduceRightOption consistent with reduceRightToOption" -> forAll(laws.reduceRightOptionConsistentWithReduceRightToOption[A] _), "get reference" -> forAll(laws.getRef[A] _), - "fold reference" -> forAll(laws.getRef[A] _), - "toList reference" -> forAll(laws.getRef[A] _), - "filter_ reference" -> forAll(laws.getRef[A] _), - "takeWhile_ reference" -> forAll(laws.getRef[A] _), - "dropWhile_ reference" -> forAll(laws.getRef[A] _) + "fold reference" -> forAll(laws.foldRef[A] _), + "toList reference" -> forAll(laws.toListRef[A] _), + "filter_ reference" -> forAll(laws.filter_Ref[A] _), + "takeWhile_ reference" -> forAll(laws.takeWhile_Ref[A] _), + "dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala index d127b54ea9..afe6182ff3 100644 --- a/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala @@ -23,6 +23,7 @@ trait NonEmptyTraverseTests[F[_]] extends TraverseTests[F] with ReducibleTests[F CogenC: Cogen[C], CogenM: Cogen[M], M: Monoid[M], + MA: Monoid[A], MB: Monoid[B], EqFA: Eq[F[A]], EqFC: Eq[F[C]], diff --git a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala index 91bc6371a1..df9edce95b 100644 --- a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala @@ -22,6 +22,7 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] { EqA: Eq[A], EqB: Eq[B], EqOptionA: Eq[Option[A]], + MonoidA: Monoid[A], MonoidB: Monoid[B] ): RuleSet = new DefaultRuleSet( diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index 2ddba1c471..7cb5b1be4f 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -21,9 +21,11 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { CogenC: Cogen[C], CogenM: Cogen[M], M: Monoid[M], + MA: Monoid[A], EqFA: Eq[F[A]], EqFC: Eq[F[C]], EqM: Eq[M], + EqA: Eq[A], EqXYFC: Eq[X[Y[F[C]]]], EqXFB: Eq[X[F[B]]], EqYFB: Eq[Y[F[B]]],