From 706effc1504b129a085bb2db2884455422a88d74 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 04:36:13 -0400 Subject: [PATCH 1/7] Fix many cats-kernel instances. This commit does a bunch of things: 1. Move relevant either/function instnaces into kernel 2. Add missing Eq/PartialOrder instances for Either 3. Add many missing instances for Function0/Function1 4. Add instances for BitSet 5. Remove code duplication between collection monoids 6. Improve test coverage of function instances 7. Add some missing package objects in kernel 8. A few stylistic changes This is a relatively large commit but I think almost all of this is stuff we'd like to get done before we freeze cats-kernel. --- .../scala/cats/instances/bigDecimal.scala | 2 +- .../main/scala/cats/instances/either.scala | 44 +----- .../main/scala/cats/instances/function.scala | 55 ++----- core/src/main/scala/cats/instances/list.scala | 3 +- core/src/main/scala/cats/instances/map.scala | 10 +- .../main/scala/cats/instances/vector.scala | 3 +- .../cats/kernel/instances/StaticMethods.scala | 11 ++ .../scala/cats/kernel/instances/all.scala | 2 + .../scala/cats/kernel/instances/bitSet.scala | 31 ++++ .../scala/cats/kernel/instances/double.scala | 2 + .../scala/cats/kernel/instances/either.scala | 71 +++++++++ .../scala/cats/kernel/instances/float.scala | 2 + .../cats/kernel/instances/function.scala | 133 +++++++++++++++++ .../scala/cats/kernel/instances/list.scala | 19 +-- .../scala/cats/kernel/instances/set.scala | 2 +- .../scala/cats/kernel/instances/stream.scala | 20 +-- .../scala/cats/kernel/instances/vector.scala | 20 +-- .../test/scala/cats/tests/FunctionTests.scala | 63 +++++++- tests/src/test/scala/cats/tests/Helpers.scala | 137 ++++++++++++++++++ 19 files changed, 486 insertions(+), 144 deletions(-) create mode 100644 kernel/src/main/scala/cats/kernel/instances/bitSet.scala create mode 100644 kernel/src/main/scala/cats/kernel/instances/either.scala create mode 100644 kernel/src/main/scala/cats/kernel/instances/function.scala create mode 100644 tests/src/test/scala/cats/tests/Helpers.scala diff --git a/core/src/main/scala/cats/instances/bigDecimal.scala b/core/src/main/scala/cats/instances/bigDecimal.scala index 9acba3fc35..a12779cd90 100644 --- a/core/src/main/scala/cats/instances/bigDecimal.scala +++ b/core/src/main/scala/cats/instances/bigDecimal.scala @@ -1,7 +1,7 @@ package cats package instances -trait BigDecimalInstances { +trait BigDecimalInstances extends cats.kernel.instances.BigDecimalInstances { implicit val catsStdShowForBigDecimal: Show[BigDecimal] = Show.fromToString[BigDecimal] } diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index d5b3cd628f..4a97f39dbe 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -5,7 +5,7 @@ import cats.syntax.EitherUtil import cats.syntax.either._ import scala.annotation.tailrec -trait EitherInstances extends EitherInstances1 { +trait EitherInstances extends cats.kernel.instances.EitherInstances { implicit val catsStdBitraverseForEither: Bitraverse[Either] = new Bitraverse[Either] { def bitraverse[G[_], A, B, C, D](fab: Either[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Either[C, D]] = @@ -81,27 +81,6 @@ trait EitherInstances extends EitherInstances1 { } // scalastyle:on method.length - implicit def catsStdOrderForEither[A, B](implicit A: Order[A], B: Order[B]): Order[Either[A, B]] = new Order[Either[A, B]] { - def compare(x: Either[A, B], y: Either[A, B]): Int = x.fold( - a => y.fold(A.compare(a, _), _ => -1), - b => y.fold(_ => 1, B.compare(b, _)) - ) - } - - implicit def catsStdShowForEither[A, B](implicit A: Show[A], B: Show[B]): Show[Either[A, B]] = - new Show[Either[A, B]] { - def show(f: Either[A, B]): String = f.fold( - a => s"Left(${A.show(a)})", - b => s"Right(${B.show(b)})" - ) - } - - implicit def catsDataMonoidForEither[A, B](implicit B: Monoid[B]): Monoid[Either[A, B]] = - new Monoid[Either[A, B]] { - def empty: Either[A, B] = Right(B.empty) - def combine(x: Either[A, B], y: Either[A, B]): Either[A, B] = x combine y - } - implicit def catsDataSemigroupKForEither[L]: SemigroupK[Either[L, ?]] = new SemigroupK[Either[L, ?]] { def combineK[A](x: Either[L, A], y: Either[L, A]): Either[L, A] = x match { @@ -109,23 +88,12 @@ trait EitherInstances extends EitherInstances1 { case Right(_) => x } } -} -private[instances] sealed trait EitherInstances1 extends EitherInstances2 { - implicit def catsStdPartialOrderForEither[A, B](implicit A: PartialOrder[A], B: PartialOrder[B]): PartialOrder[Either[A, B]] = - new PartialOrder[Either[A, B]] { - def partialCompare(x: Either[A, B], y: Either[A, B]): Double = x.fold( - a => y.fold(A.partialCompare(a, _), _ => -1), - b => y.fold(_ => 1, B.partialCompare(b, _)) + implicit def catsStdShowForEither[A, B](implicit A: Show[A], B: Show[B]): Show[Either[A, B]] = + new Show[Either[A, B]] { + def show(f: Either[A, B]): String = f.fold( + a => s"Left(${A.show(a)})", + b => s"Right(${B.show(b)})" ) } } - -private[instances] sealed trait EitherInstances2 { - implicit def catsStdEqForEither[A, B](implicit A: Eq[A], B: Eq[B]): Eq[Either[A, B]] = new Eq[Either[A, B]] { - def eqv(x: Either[A, B], y: Either[A, B]): Boolean = x.fold( - a => y.fold(A.eqv(a, _), _ => false), - b => y.fold(_ => false, B.eqv(b, _)) - ) - } -} diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index bf01a45fe9..ffe13e5bcb 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -5,6 +5,10 @@ import cats.arrow.{Arrow, Choice} import cats.functor.Contravariant import annotation.tailrec + +trait FunctionInstances extends cats.kernel.instances.FunctionInstances + with Function0Instances with Function1Instances + private[instances] sealed trait Function0Instances { implicit val catsStdBimonadForFunction0: Bimonad[Function0] with RecursiveTailRecM[Function0] = @@ -29,14 +33,9 @@ private[instances] sealed trait Function0Instances { loop(a) } } - - implicit def catsStdEqForFunction0[A](implicit A: Eq[A]): Eq[() => A] = - new Eq[() => A] { - def eqv(x: () => A, y: () => A): Boolean = A.eqv(x(), y()) - } } -private[instances] sealed trait Function1Instances extends Function1Instances0 { +private[instances] sealed trait Function1Instances { implicit def catsStdContravariantForFunction1[R]: Contravariant[? => R] = new Contravariant[? => R] { def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = @@ -91,43 +90,9 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { def compose[A, B, C](f: B => C, g: A => B): A => C = f.compose(g) } - implicit def catsStdMonoidForFunction1[A, B](implicit M: Monoid[B]): Monoid[A => B] = - new Function1Monoid[A, B] { def B: Monoid[B] = M } - - implicit val catsStdMonoidKForFunction1: MonoidK[λ[α => α => α]] = - new Function1MonoidK {} -} - -private[instances] sealed trait Function1Instances0 { - implicit def catsStdSemigroupForFunction1[A, B](implicit S: Semigroup[B]): Semigroup[A => B] = - new Function1Semigroup[A, B] { def B: Semigroup[B] = S } - - implicit val catsStdSemigroupKForFunction1: SemigroupK[λ[α => α => α]] = - new Function1SemigroupK {} -} - -private[instances] sealed trait Function1Semigroup[A, B] extends Semigroup[A => B] { - implicit def B: Semigroup[B] - - override def combine(x: A => B, y: A => B): A => B = { a => - B.combine(x(a), y(a)) - } -} - -private[instances] sealed trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { - implicit def B: Monoid[B] - - override def empty: A => B = _ => B.empty -} - -private[instances] sealed trait Function1SemigroupK extends SemigroupK[λ[α => α => α]] { - override def combineK[A](x: A => A, y: A => A): A => A = x compose y -} - -private[instances] sealed trait Function1MonoidK extends MonoidK[λ[α => α => α]] with Function1SemigroupK { - override def empty[A]: A => A = identity[A] + implicit val catsStdMonoidKForFunction1: MonoidK[λ[α => Function1[α, α]]] = + new MonoidK[λ[α => Function1[α, α]]] { + def empty[A]: A => A = identity[A] + def combineK[A](x: A => A, y: A => A): A => A = x compose y + } } - -trait FunctionInstances - extends Function0Instances - with Function1Instances diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index cfbfc7cf0d..bf982d8eed 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -84,6 +84,7 @@ trait ListInstances extends cats.kernel.instances.ListInstances { implicit def catsStdShowForList[A:Show]: Show[List[A]] = new Show[List[A]] { - def show(fa: List[A]): String = fa.map(_.show).mkString("List(", ", ", ")") + def show(fa: List[A]): String = + fa.iterator.map(_.show).mkString("List(", ", ", ")") } } diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index 166f33b9c3..682a627b36 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -6,11 +6,11 @@ import scala.annotation.tailrec trait MapInstances extends cats.kernel.instances.MapInstances { implicit def catsStdShowForMap[A, B](implicit showA: Show[A], showB: Show[B]): Show[Map[A, B]] = - Show.show[Map[A, B]] { m => - val body = m.map { case (a, b) => - s"${showA.show(a)} -> ${showB.show(b)})" - }.mkString(",") - s"Map($body)" + new Show[Map[A, B]] { + def show(m: Map[A, B]): String = + m.iterator + .map { case (a, b) => showA.show(a) + " -> " + showB.show(b) } + .mkString("Map(", ", ", ")") } // scalastyle:off method.length diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 8126233b32..cdbf80f94d 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -90,6 +90,7 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { implicit def catsStdShowForVector[A:Show]: Show[Vector[A]] = new Show[Vector[A]] { - def show(fa: Vector[A]): String = fa.map(_.show).mkString("Vector(", ", ", ")") + def show(fa: Vector[A]): String = + fa.iterator.map(_.show).mkString("Vector(", ", ", ")") } } diff --git a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala index 66f4cd3af6..7aca9f6170 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala @@ -68,4 +68,15 @@ object StaticMethods { true } // scalastyle:on return + + def combineNIterable[A, R](b: mutable.Builder[A, R], x: Iterable[A], n: Int): R = { + var i = n + while (i > 0) { b ++= x; i -= 1 } + b.result + } + + def combineAllIterable[A, R](b: mutable.Builder[A, R], xs: TraversableOnce[Iterable[A]]): R = { + xs.foreach(b ++= _) + b.result + } } diff --git a/kernel/src/main/scala/cats/kernel/instances/all.scala b/kernel/src/main/scala/cats/kernel/instances/all.scala index b519278ef1..8ca6d5348d 100644 --- a/kernel/src/main/scala/cats/kernel/instances/all.scala +++ b/kernel/src/main/scala/cats/kernel/instances/all.scala @@ -6,11 +6,13 @@ package object all extends AllInstances trait AllInstances extends BigDecimalInstances with BigIntInstances + with BitSetInstances with BooleanInstances with ByteInstances with CharInstances with DoubleInstances with FloatInstances + with FunctionInstances with IntInstances with ListInstances with LongInstances diff --git a/kernel/src/main/scala/cats/kernel/instances/bitSet.scala b/kernel/src/main/scala/cats/kernel/instances/bitSet.scala new file mode 100644 index 0000000000..a84af1f4cf --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/instances/bitSet.scala @@ -0,0 +1,31 @@ +package cats.kernel +package instances + +import scala.collection.immutable.BitSet + +package object bitSet extends BitSetInstances + +trait BitSetInstances { + implicit val catsKernelStdPartialOrderForBitSet: PartialOrder[BitSet] = + new BitSetPartialOrder + + implicit val catsKernelStdSemilatticeForBitSet: BoundedSemilattice[BitSet] = + new BitSetSemilattice +} + +class BitSetPartialOrder extends PartialOrder[BitSet] { + def partialCompare(x: BitSet, y: BitSet): Double = + if (x eq y) 0.0 + else if (x.size < y.size) if (x.subsetOf(y)) -1.0 else Double.NaN + else if (y.size < x.size) if (y.subsetOf(x)) 1.0 else Double.NaN + else if (x == y) 0.0 + else Double.NaN + + override def eqv(x: BitSet, y: BitSet): Boolean = + x == y +} + +class BitSetSemilattice extends BoundedSemilattice[BitSet] { + def empty: BitSet = BitSet.empty + def combine(x: BitSet, y: BitSet): BitSet = x | y +} diff --git a/kernel/src/main/scala/cats/kernel/instances/double.scala b/kernel/src/main/scala/cats/kernel/instances/double.scala index 93e616cfe4..b4a6527fa9 100644 --- a/kernel/src/main/scala/cats/kernel/instances/double.scala +++ b/kernel/src/main/scala/cats/kernel/instances/double.scala @@ -3,6 +3,8 @@ package instances import java.lang.Math +package object double extends DoubleInstances + trait DoubleInstances { implicit val catsKernelStdOrderForDouble: Order[Double] = new DoubleOrder implicit val catsKernelStdGroupForDouble: CommutativeGroup[Double] = new DoubleGroup diff --git a/kernel/src/main/scala/cats/kernel/instances/either.scala b/kernel/src/main/scala/cats/kernel/instances/either.scala new file mode 100644 index 0000000000..58ac6957dc --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/instances/either.scala @@ -0,0 +1,71 @@ +package cats.kernel +package instances + +package object either extends EitherInstances + +trait EitherInstances extends EitherInstances0 { + + implicit def catsStdOrderForEither[A, B](implicit A: Order[A], B: Order[B]): Order[Either[A, B]] = + new Order[Either[A, B]] { + def compare(x: Either[A, B], y: Either[A, B]): Int = + x match { + case Left(xx) => y match { + case Left(yy) => A.compare(xx, yy) + case Right(_) => -1 + } + case Right(xx) => y match { + case Left(_) => 1 + case Right(yy) => B.compare(xx, yy) + } + } + } + + implicit def catsDataMonoidForEither[A, B](implicit B: Monoid[B]): Monoid[Either[A, B]] = + new Monoid[Either[A, B]] { + def empty: Either[A, B] = + Right(B.empty) + def combine(x: Either[A, B], y: Either[A, B]): Either[A, B] = + x match { + case left @ Left(_) => left + case Right(xx) => y match { + case left @ Left(_) => left + case Right(yy) => Right(B.combine(xx, yy)) + } + } + } +} + +trait EitherInstances0 extends EitherInstances1 { + + implicit def catsStdPartialOrderForEither[A, B](implicit A: PartialOrder[A], B: PartialOrder[B]): PartialOrder[Either[A, B]] = + new PartialOrder[Either[A, B]] { + def partialCompare(x: Either[A, B], y: Either[A, B]): Double = + x match { + case Left(xx) => y match { + case Left(yy) => A.partialCompare(xx, yy) + case Right(_) => -1.0 + } + case Right(xx) => y match { + case Left(_) => 1.0 + case Right(yy) => B.partialCompare(xx, yy) + } + } + } +} + +trait EitherInstances1 { + implicit def catsStdEqForEither[A, B](implicit A: Eq[A], B: Eq[B]): Eq[Either[A, B]] = + new Eq[Either[A, B]] { + def eqv(x: Either[A, B], y: Either[A, B]): Boolean = + x match { + case Left(xx) => y match { + case Left(yy) => A.eqv(xx, yy) + case Right(_) => false + } + case Right(xx) => y match { + case Left(_) => false + case Right(yy) => B.eqv(xx, yy) + } + } + } +} diff --git a/kernel/src/main/scala/cats/kernel/instances/float.scala b/kernel/src/main/scala/cats/kernel/instances/float.scala index da977b7418..b1c0e15b76 100644 --- a/kernel/src/main/scala/cats/kernel/instances/float.scala +++ b/kernel/src/main/scala/cats/kernel/instances/float.scala @@ -1,6 +1,8 @@ package cats.kernel package instances +package object float extends FloatInstances + trait FloatInstances { implicit val catsKernelStdOrderForFloat: Order[Float] = new FloatOrder implicit val catsKernelStdGroupForFloat: CommutativeGroup[Float] = new FloatGroup diff --git a/kernel/src/main/scala/cats/kernel/instances/function.scala b/kernel/src/main/scala/cats/kernel/instances/function.scala new file mode 100644 index 0000000000..97a00ed9ab --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/instances/function.scala @@ -0,0 +1,133 @@ +package cats.kernel +package instances + +package object function extends FunctionInstances + +trait FunctionInstances extends FunctionInstances0 { + + implicit def catsKernelOrderForFunction0[A](implicit ev: Order[A]): Order[() => A] = + new Order[() => A] { + def compare(x: () => A, y: () => A): Int = ev.compare(x(), y()) + } + + implicit def catsKernelCommutativeGroupForFunction0[A](implicit G: CommutativeGroup[A]): CommutativeGroup[() => A] = + new Function0Group[A] with CommutativeGroup[() => A] { def A: Group[A] = G } + + implicit def catsKernelCommutativeGroupForFunction1[A, B](implicit G: CommutativeGroup[B]): CommutativeGroup[A => B] = + new Function1Group[A, B] with CommutativeGroup[A => B] { def B: Group[B] = G } +} + +trait FunctionInstances0 extends FunctionInstances1 { + + implicit def catsKernelPartialOrderForFunction0[A](implicit ev: PartialOrder[A]): PartialOrder[() => A] = + new PartialOrder[() => A] { + def partialCompare(x: () => A, y: () => A): Double = ev.partialCompare(x(), y()) + } + + implicit def catsKernelGroupForFunction0[A](implicit G: Group[A]): Group[() => A] = + new Function0Group[A] { def A: Group[A] = G } + + implicit def catsKernelGroupForFunction1[A, B](implicit G: Group[B]): Group[A => B] = + new Function1Group[A, B] { def B: Group[B] = G } + + implicit def catsKernelBoundedSemilatticeForFunction0[A](implicit G: BoundedSemilattice[A]): BoundedSemilattice[() => A] = + new Function0Monoid[A] with BoundedSemilattice[() => A] { def A: Monoid[A] = G } + + implicit def catsKernelBoundedSemilatticeForFunction1[A, B](implicit G: BoundedSemilattice[B]): BoundedSemilattice[A => B] = + new Function1Monoid[A, B] with BoundedSemilattice[A => B] { def B: Monoid[B] = G } +} + +trait FunctionInstances1 extends FunctionInstances2 { + + implicit def catsKernelEqForFunction0[A](implicit ev: Eq[A]): Eq[() => A] = + new Eq[() => A] { + def eqv(x: () => A, y: () => A): Boolean = ev.eqv(x(), y()) + } + + implicit def catsKernelCommutativeMonoidForFunction0[A](implicit M: CommutativeMonoid[A]): CommutativeMonoid[() => A] = + new Function0Monoid[A] with CommutativeMonoid[() => A] { def A: Monoid[A] = M } + + implicit def catsKernelCommutativeMonoidForFunction1[A, B](implicit M: CommutativeMonoid[B]): CommutativeMonoid[A => B] = + new Function1Monoid[A, B] with CommutativeMonoid[A => B] { def B: Monoid[B] = M } + + implicit def catsKernelSemilatticeForFunction0[A](implicit M: Semilattice[A]): Semilattice[() => A] = + new Function0Semigroup[A] with Semilattice[() => A] { def A: Semigroup[A] = M } + + implicit def catsKernelSemilatticeForFunction1[A, B](implicit M: Semilattice[B]): Semilattice[A => B] = + new Function1Semigroup[A, B] with Semilattice[A => B] { def B: Semigroup[B] = M } +} + +trait FunctionInstances2 extends FunctionInstances3 { + + implicit def catsKernelMonoidForFunction0[A](implicit M: Monoid[A]): Monoid[() => A] = + new Function0Monoid[A] { def A: Monoid[A] = M } + + implicit def catsKernelMonoidForFunction1[A, B](implicit M: Monoid[B]): Monoid[A => B] = + new Function1Monoid[A, B] { def B: Monoid[B] = M } + + implicit def catsKernelBandForFunction0[A](implicit S: Band[A]): Band[() => A] = + new Function0Semigroup[A] with Band[() => A] { def A: Semigroup[A] = S } + + implicit def catsKernelBandForFunction1[A, B](implicit S: Band[B]): Band[A => B] = + new Function1Semigroup[A, B] with Band[A => B] { def B: Semigroup[B] = S } +} + +trait FunctionInstances3 extends FunctionInstances4 { + + implicit def catsKernelCommutativeSemigroupForFunction0[A](implicit S: CommutativeSemigroup[A]): CommutativeSemigroup[() => A] = + new Function0Semigroup[A] with CommutativeSemigroup[() => A] { def A: Semigroup[A] = S } + + implicit def catsKernelCommutativeSemigroupForFunction1[A, B](implicit S: CommutativeSemigroup[B]): CommutativeSemigroup[A => B] = + new Function1Semigroup[A, B] with CommutativeSemigroup[A => B] { def B: Semigroup[B] = S } +} + +trait FunctionInstances4 { + + implicit def catsKernelSemigroupForFunction0[A](implicit S: Semigroup[A]): Semigroup[() => A] = + new Function0Semigroup[A] { def A: Semigroup[A] = S } + + implicit def catsKernelSemigroupForFunction1[A, B](implicit S: Semigroup[B]): Semigroup[A => B] = + new Function1Semigroup[A, B] { def B: Semigroup[B] = S } +} + +trait Function1Semigroup[A, B] extends Semigroup[A => B] { + implicit def B: Semigroup[B] + + override def combine(x: A => B, y: A => B): A => B = + (a: A) => B.combine(x(a), y(a)) +} + +trait Function1Monoid[A, B] extends Function1Semigroup[A, B] with Monoid[A => B] { + implicit def B: Monoid[B] + + val empty: A => B = + (_: A) => B.empty +} + +trait Function1Group[A, B] extends Function1Monoid[A, B] with Group[A => B] { + implicit def B: Group[B] + + def inverse(x: A => B): A => B = + (a: A) => B.inverse(x(a)) +} + +trait Function0Semigroup[A] extends Semigroup[() => A] { + implicit def A: Semigroup[A] + + override def combine(x: () => A, y: () => A): () => A = + () => A.combine(x(), y()) +} + +trait Function0Monoid[A] extends Function0Semigroup[A] with Monoid[() => A] { + implicit def A: Monoid[A] + + val empty: () => A = + () => A.empty +} + +trait Function0Group[A] extends Function0Monoid[A] with Group[() => A] { + implicit def A: Group[A] + + def inverse(x: () => A): () => A = + () => A.inverse(x()) +} diff --git a/kernel/src/main/scala/cats/kernel/instances/list.scala b/kernel/src/main/scala/cats/kernel/instances/list.scala index 7a5d018956..9f19482289 100644 --- a/kernel/src/main/scala/cats/kernel/instances/list.scala +++ b/kernel/src/main/scala/cats/kernel/instances/list.scala @@ -2,7 +2,6 @@ package cats.kernel package instances import scala.annotation.tailrec -import scala.collection.mutable package object list extends ListInstances @@ -81,19 +80,9 @@ class ListMonoid[A] extends Monoid[List[A]] { def empty: List[A] = Nil def combine(x: List[A], y: List[A]): List[A] = x ::: y - override def combineN(x: List[A], n: Int): List[A] = { - val buf = mutable.ListBuffer.empty[A] - var i = n - while (i > 0) { - buf ++= x - i -= 1 - } - buf.toList - } + override def combineN(x: List[A], n: Int): List[A] = + StaticMethods.combineNIterable(List.newBuilder[A], x, n) - override def combineAll(xs: TraversableOnce[List[A]]): List[A] = { - val buf = mutable.ListBuffer.empty[A] - xs.foreach(buf ++= _) - buf.toList - } + override def combineAll(xs: TraversableOnce[List[A]]): List[A] = + StaticMethods.combineAllIterable(List.newBuilder[A], xs) } diff --git a/kernel/src/main/scala/cats/kernel/instances/set.scala b/kernel/src/main/scala/cats/kernel/instances/set.scala index cff59d7ce3..735f3edd1a 100644 --- a/kernel/src/main/scala/cats/kernel/instances/set.scala +++ b/kernel/src/main/scala/cats/kernel/instances/set.scala @@ -15,7 +15,7 @@ class SetPartialOrder[A] extends PartialOrder[Set[A]] { def partialCompare(x: Set[A], y: Set[A]): Double = if (x eq y) 0.0 else if (x.size < y.size) if (x.subsetOf(y)) -1.0 else Double.NaN - else if (y.size < x.size) -partialCompare(y, x) + else if (y.size < x.size) if (y.subsetOf(x)) 1.0 else Double.NaN else if (x == y) 0.0 else Double.NaN diff --git a/kernel/src/main/scala/cats/kernel/instances/stream.scala b/kernel/src/main/scala/cats/kernel/instances/stream.scala index 027ea4795c..8561997b94 100644 --- a/kernel/src/main/scala/cats/kernel/instances/stream.scala +++ b/kernel/src/main/scala/cats/kernel/instances/stream.scala @@ -42,19 +42,9 @@ class StreamMonoid[A] extends Monoid[Stream[A]] { def empty: Stream[A] = Stream.empty def combine(x: Stream[A], y: Stream[A]): Stream[A] = x ++ y - override def combineN(x: Stream[A], n: Int): Stream[A] = { - val buf = Stream.newBuilder[A] - var i = n - while (i > 0) { - buf ++= x - i -= 1 - } - buf.result - } - - override def combineAll(xs: TraversableOnce[Stream[A]]): Stream[A] = { - val buf = Stream.newBuilder[A] - xs.foreach(buf ++= _) - buf.result - } + override def combineN(x: Stream[A], n: Int): Stream[A] = + StaticMethods.combineNIterable(Stream.newBuilder[A], x, n) + + override def combineAll(xs: TraversableOnce[Stream[A]]): Stream[A] = + StaticMethods.combineAllIterable(Stream.newBuilder[A], xs) } diff --git a/kernel/src/main/scala/cats/kernel/instances/vector.scala b/kernel/src/main/scala/cats/kernel/instances/vector.scala index 128bbebe16..c0a191f2df 100644 --- a/kernel/src/main/scala/cats/kernel/instances/vector.scala +++ b/kernel/src/main/scala/cats/kernel/instances/vector.scala @@ -42,19 +42,9 @@ class VectorMonoid[A] extends Monoid[Vector[A]] { def empty: Vector[A] = Vector.empty def combine(x: Vector[A], y: Vector[A]): Vector[A] = x ++ y - override def combineN(x: Vector[A], n: Int): Vector[A] = { - val buf = Vector.newBuilder[A] - var i = n - while (i > 0) { - buf ++= x - i -= 1 - } - buf.result - } - - override def combineAll(xs: TraversableOnce[Vector[A]]): Vector[A] = { - val buf = Vector.newBuilder[A] - xs.foreach(buf ++= _) - buf.result - } + override def combineN(x: Vector[A], n: Int): Vector[A] = + StaticMethods.combineNIterable(Vector.newBuilder[A], x, n) + + override def combineAll(xs: TraversableOnce[Vector[A]]): Vector[A] = + StaticMethods.combineAllIterable(Vector.newBuilder[A], xs) } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 4a13cd0f05..e545030f2d 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -6,10 +6,14 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ -import cats.kernel.laws.GroupLaws +import cats.kernel.laws.{ GroupLaws, OrderLaws } +import cats.kernel.{ CommutativeSemigroup, CommutativeMonoid, CommutativeGroup } +import cats.kernel.{ Band, Semilattice, BoundedSemilattice } class FunctionTests extends CatsSuite { + import Helpers._ + checkAll("Function0[Int]", CartesianTests[Function0].cartesian[Int, Int, Int]) checkAll("Cartesian[Function0]", SerializableTests.serializable(Cartesian[Function0])) @@ -32,13 +36,58 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) - checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup(catsStdSemigroupForFunction1[String, Int])) + checkAll("Function1[Int, Int]", MonoidKTests[λ[α => α => α]].monoidK[Int]) + checkAll("MonoidK[λ[α => α => α]", SerializableTests.serializable(catsStdMonoidKForFunction1)) - checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) - checkAll("Function1[Int, Int]", MonoidKTests[λ[α => α => α]].semigroupK[Int]) - checkAll("SemigroupK[λ[α => α => α]", SerializableTests.serializable(catsStdSemigroupKForFunction1)) + // law checks for the various Function0-related instances + checkAll("Function0[Eqed]", OrderLaws[Function0[Eqed]].eqv) + checkAll("Function0[POrd]", OrderLaws[Function0[POrd]].partialOrder) + checkAll("Function0[Ord]", OrderLaws[Function0[Ord]].order) + checkAll("Function0[Semi]", GroupLaws[Function0[Semi]].semigroup) + checkAll("Function0[CSemi]", GroupLaws[Function0[CSemi]].commutativeSemigroup) + checkAll("Function0[Bnd]", GroupLaws[Function0[Bnd]].band) + checkAll("Function0[SL]", GroupLaws[Function0[SL]].semilattice) + checkAll("Function0[BSL]", GroupLaws[Function0[BSL]].boundedSemilattice) + checkAll("Function0[Mono]", GroupLaws[Function0[Mono]].monoid) + checkAll("Function0[CMono]", GroupLaws[Function0[CMono]].commutativeMonoid) + checkAll("Function0[Grp]", GroupLaws[Function0[Grp]].group) + checkAll("Function0[CGrp]", GroupLaws[Function0[CGrp]].commutativeGroup) - checkAll("Function1[Int, Int]", MonoidKTests[λ[α => α => α]].monoidK[Int]) - checkAll("MonoidK[λ[α => α => α]", SerializableTests.serializable(catsStdMonoidKForFunction1)) + // serialization tests for the various Function0-related instances + checkAll("Eq[() => Eqed]", SerializableTests.serializable(Eq[() => Eqed])) + checkAll("PartialOrder[() => POrd]", SerializableTests.serializable(PartialOrder[() => POrd])) + checkAll("Order[() => Ord]", SerializableTests.serializable(Order[() => Ord])) + checkAll("Semigroup[() => Semi]", SerializableTests.serializable(Semigroup[() => Semi])) + checkAll("CommutativeSemigroup[() => Semi]", SerializableTests.serializable(CommutativeSemigroup[() => CSemi])) + checkAll("Band[() => Bnd]", SerializableTests.serializable(Band[() => Bnd])) + checkAll("Semilattice[() => SL]", SerializableTests.serializable(Semilattice[() => SL])) + checkAll("BoundedSemilattice[() => BSL]", SerializableTests.serializable(BoundedSemilattice[() => BSL])) + checkAll("Monoid[() => Mono]", SerializableTests.serializable(Monoid[() => Mono])) + checkAll("CommutativeMonoid[() => CMono]", SerializableTests.serializable(CommutativeMonoid[() => CMono])) + checkAll("Group[() => Grp]", SerializableTests.serializable(Group[() => Grp])) + checkAll("CommutativeGroup[() => CGrp]", SerializableTests.serializable(CommutativeGroup[() => CGrp])) + + + // law checks for the various Function1-related instances + checkAll("Function1[String, Semi]", GroupLaws[Function1[String, Semi]].semigroup) + checkAll("Function1[String, CSemi]", GroupLaws[Function1[String, CSemi]].commutativeSemigroup) + checkAll("Function1[String, Bnd]", GroupLaws[Function1[String, Bnd]].band) + checkAll("Function1[String, SL]", GroupLaws[Function1[String, SL]].semilattice) + checkAll("Function1[String, BSL]", GroupLaws[Function1[String, BSL]].boundedSemilattice) + checkAll("Function1[String, Mono]", GroupLaws[Function1[String, Mono]].monoid) + checkAll("Function1[String, CMono]", GroupLaws[Function1[String, CMono]].commutativeMonoid) + checkAll("Function1[String, Grp]", GroupLaws[Function1[String, Grp]].group) + checkAll("Function1[String, CGrp]", GroupLaws[Function1[String, CGrp]].commutativeGroup) + + // serialization tests for the various Function1-related instances + checkAll("Semigroup[String => Semi]", SerializableTests.serializable(Semigroup[String => Semi])) + checkAll("CommutativeSemigroup[String => Semi]", SerializableTests.serializable(CommutativeSemigroup[String => CSemi])) + checkAll("Band[String => Bnd]", SerializableTests.serializable(Band[String => Bnd])) + checkAll("Semilattice[String => SL]", SerializableTests.serializable(Semilattice[String => SL])) + checkAll("BoundedSemilattice[String => BSL]", SerializableTests.serializable(BoundedSemilattice[String => BSL])) + checkAll("Monoid[String => Mono]", SerializableTests.serializable(Monoid[String => Mono])) + checkAll("CommutativeMonoid[String => CMono]", SerializableTests.serializable(CommutativeMonoid[String => CMono])) + checkAll("Group[String => Grp]", SerializableTests.serializable(Group[String => Grp])) + checkAll("CommutativeGroup[String => CGrp]", SerializableTests.serializable(CommutativeGroup[String => CGrp])) } diff --git a/tests/src/test/scala/cats/tests/Helpers.scala b/tests/src/test/scala/cats/tests/Helpers.scala new file mode 100644 index 0000000000..ed88d386b2 --- /dev/null +++ b/tests/src/test/scala/cats/tests/Helpers.scala @@ -0,0 +1,137 @@ +package cats +package tests + +import org.scalacheck.Arbitrary +import Arbitrary.arbitrary + +import cats.kernel.{ CommutativeSemigroup, CommutativeMonoid, CommutativeGroup } +import cats.kernel.{ Band, Semilattice, BoundedSemilattice } + +/** + * Helpers provides new concrete types where we control exactly which + * type class instances are available. For example, the SL type has: + * + * - Semilattice[SL] + * - Arbitrary[SL] + * - Eq[SL] + * + * (All types in Helpers have Arbitrary and Eq instances.) + * + * These are useful when a type constructor (e.g. Function0) can + * produce many different instances depending on which instances are + * available for its type parameter. + */ +object Helpers { + + abstract class Arb[E](f: Int => E) { + implicit val earb: Arbitrary[E] = Arbitrary(arbitrary[Int].map(f)) + } + + trait Q[E] { + implicit val eeq: Eq[E] = Eq.fromUniversalEquals + } + + abstract class Companion[E](f: Int => E) extends Arb[E](f) with Q[E] + + // Eq + case class Eqed(n: Int) + object Eqed extends Companion(new Eqed(_)) + + // PartialOrder + case class POrd(n: Int) + object POrd extends Arb(new POrd(_)) { + implicit object O extends PartialOrder[POrd] { + def partialCompare(x: POrd, y: POrd): Double = + if (x.n >= 0 && y.n >= 0) (x.n compare y.n).toDouble + else if (x.n <= 0 && y.n <= 0) (y.n compare x.n).toDouble + else Double.NaN + } + } + + // Order + case class Ord(n: Int) + object Ord extends Arb(new Ord(_)) { + implicit object O extends Order[Ord] { + def compare(x: Ord, y: Ord): Int = x.n compare y.n + } + } + + // Band + case class Bnd(n: Int) + object Bnd extends Companion(new Bnd(_)) { + implicit object Alg extends Band[Bnd] { + def combine(x: Bnd, y: Bnd): Bnd = Bnd(x.n & y.n) + } + } + + // Semilattice + case class SL(n: Int) + object SL extends Companion(new SL(_)) { + implicit object Alg extends Semilattice[SL] { + def combine(x: SL, y: SL): SL = SL(x.n & y.n) + } + } + + // BoundedSemilattice + case class BSL(n: Int) + object BSL extends Companion(new BSL(_)) { + implicit object Alg extends BoundedSemilattice[BSL] { + def empty: BSL = BSL(0) + def combine(x: BSL, y: BSL): BSL = BSL(x.n | y.n) + } + } + + // Semigroup + case class Semi(n: Int) + object Semi extends Companion(new Semi(_)) { + implicit object Alg extends Semigroup[Semi] { + def combine(x: Semi, y: Semi): Semi = Semi(x.n ^ y.n) + } + } + + // CommutativeSemigroup + case class CSemi(n: Int) + object CSemi extends Companion(new CSemi(_)) { + implicit object Alg extends CommutativeSemigroup[CSemi] { + def combine(x: CSemi, y: CSemi): CSemi = CSemi(x.n ^ y.n) + } + } + + // Monoid + case class Mono(n: Int) + object Mono extends Companion(new Mono(_)) { + implicit object Alg extends Monoid[Mono] { + def empty: Mono = Mono(Int.MaxValue) + def combine(x: Mono, y: Mono): Mono = Mono(x.n min y.n) + } + } + + // CommutativeMonoid + case class CMono(n: Int) + object CMono extends Companion(new CMono(_)) { + implicit object Alg extends CommutativeMonoid[CMono] { + def empty: CMono = CMono(Int.MaxValue) + def combine(x: CMono, y: CMono): CMono = CMono(x.n min y.n) + } + } + + // Group + case class Grp(n: Int) + object Grp extends Companion(new Grp(_)) { + implicit object Alg extends Group[Grp] { + def empty: Grp = Grp(0) + def combine(x: Grp, y: Grp): Grp = Grp(x.n + y.n) + def inverse(x: Grp): Grp = Grp(-x.n) + } + } + + // CommutativeGroup + case class CGrp(n: Int) + object CGrp extends Companion(new CGrp(_)) { + implicit object Alg extends CommutativeGroup[CGrp] { + def empty: CGrp = CGrp(0) + def combine(x: CGrp, y: CGrp): CGrp = CGrp(x.n + y.n) + def inverse(x: CGrp): CGrp = CGrp(-x.n) + } + } +} From aeee001cb366cfbc96651023e5b6b63ce94a18c3 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 10:33:26 -0400 Subject: [PATCH 2/7] Check BitSet instances (and Set's semilattice). --- .../src/test/scala/cats/kernel/laws/LawTests.scala | 9 +++++++++ 1 file changed, 9 insertions(+) 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 4dc3f12b7e..a52e56dc3d 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -11,7 +11,9 @@ import org.typelevel.discipline.scalatest.Discipline import org.scalacheck.{ Arbitrary, Gen } import Arbitrary.arbitrary import org.scalatest.FunSuite + import scala.util.Random +import scala.collection.immutable.BitSet class LawTests extends FunSuite with Discipline { @@ -25,6 +27,9 @@ class LawTests extends FunSuite with Discipline { implicit def orderLaws[A: Eq: Arbitrary] = OrderLaws[A] implicit def groupLaws[A: Eq: Arbitrary] = GroupLaws[A] + implicit val arbitraryBitSet: Arbitrary[BitSet] = + Arbitrary(arbitrary[List[Short]].map(ns => BitSet(ns.map(_ & 0xffff): _*))) + laws[OrderLaws, Map[String, HasEq[Int]]].check(_.eqv) laws[OrderLaws, List[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv) @@ -49,6 +54,7 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, Char].check(_.order) laws[OrderLaws, Int].check(_.order) laws[OrderLaws, Long].check(_.order) + laws[OrderLaws, BitSet].check(_.partialOrder) laws[OrderLaws, BigInt].check(_.order) laws[OrderLaws, List[Int]].check(_.order) laws[OrderLaws, Option[String]].check(_.order) @@ -68,6 +74,9 @@ class LawTests extends FunSuite with Discipline { laws[GroupLaws, List[String]].check(_.monoid) laws[GroupLaws, Map[String, Int]].check(_.monoid) + laws[GroupLaws, BitSet].check(_.boundedSemilattice) + laws[GroupLaws, Set[Int]].check(_.boundedSemilattice) + laws[GroupLaws, Unit].check(_.commutativeGroup) laws[GroupLaws, Byte].check(_.commutativeGroup) laws[GroupLaws, Short].check(_.commutativeGroup) From e2e09963fb5a553598bebe4c18cd4ec6f03359ec Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 14:03:21 -0400 Subject: [PATCH 3/7] Replace Either#fold with pattern match in instances. --- .../main/scala/cats/instances/either.scala | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 4a97f39dbe..49ae082359 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -60,18 +60,25 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { } def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = - fa.fold( - a => F.pure(Left(a)), - b => F.map(f(b))(Right(_)) - ) + fa match { + case left @ Left(_) => F.pure(left) + case Right(b) => F.map(f(b))(Right(_)) + } def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = - fa.fold(_ => c, f(c, _)) + fa match { + case Left(_) => c + case Right(b) => f(c, b) + } def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = - fa.fold(_ => lc, b => f(b, lc)) + fa match { + case Left(_) => lc + case Right(b) => f(b, lc) + } - override def attempt[B](fab: Either[A, B]): Either[A, Either[A, B]] = Right(fab) + override def attempt[B](fab: Either[A, B]): Either[A, Either[A, B]] = + Right(fab) override def recover[B](fab: Either[A, B])(pf: PartialFunction[A, B]): Either[A, B] = fab recover pf override def recoverWith[B](fab: Either[A, B])(pf: PartialFunction[A, Either[A, B]]): Either[A, B] = @@ -91,9 +98,10 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { implicit def catsStdShowForEither[A, B](implicit A: Show[A], B: Show[B]): Show[Either[A, B]] = new Show[Either[A, B]] { - def show(f: Either[A, B]): String = f.fold( - a => s"Left(${A.show(a)})", - b => s"Right(${B.show(b)})" - ) + def show(x: Either[A, B]): String = + x match { + case Left(a) => "Left(" + A.show(a) + ")" + case Right(b) => "Right(" + B.show(b) + ")" + } } } From cffe60fb53fb5c7afbe4404468db4ff2e5225edb Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 14:23:25 -0400 Subject: [PATCH 4/7] Fix type mismatch. --- core/src/main/scala/cats/instances/either.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 49ae082359..ff1f2305d2 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -61,7 +61,7 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = fa match { - case left @ Left(_) => F.pure(left) + case Left(a) => F.pure(Left(a)) case Right(b) => F.map(f(b))(Right(_)) } From db2eab336466439b71ad5cdfa26a1d563cde762d Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 16:52:34 -0400 Subject: [PATCH 5/7] Add Semigroup[Either[A, B]] and save some allocations. This is for you, @johnynek. --- core/src/main/scala/cats/instances/either.scala | 6 +++--- .../main/scala/cats/kernel/instances/either.scala | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index ff1f2305d2..f962996cf3 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -48,7 +48,7 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { @tailrec def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, C]]): Either[A, C] = f(b) match { - case Left(a) => Left(a) + case left @ Left(_) => left.rightCast[C] case Right(Left(b1)) => tailRecM(b1)(f) case Right(Right(c)) => Right(c) } @@ -61,8 +61,8 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = fa match { - case Left(a) => F.pure(Left(a)) - case Right(b) => F.map(f(b))(Right(_)) + case left @ Left(_) => F.pure(left.rightCast[C]) + case Right(b) => F.map(f(b))(Right(_)) } def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = diff --git a/kernel/src/main/scala/cats/kernel/instances/either.scala b/kernel/src/main/scala/cats/kernel/instances/either.scala index 58ac6957dc..afe7f8f57d 100644 --- a/kernel/src/main/scala/cats/kernel/instances/either.scala +++ b/kernel/src/main/scala/cats/kernel/instances/either.scala @@ -37,6 +37,18 @@ trait EitherInstances extends EitherInstances0 { trait EitherInstances0 extends EitherInstances1 { + implicit def catsDataSemigroupForEither[A, B](implicit B: Semigroup[B]): Semigroup[Either[A, B]] = + new Semigroup[Either[A, B]] { + def combine(x: Either[A, B], y: Either[A, B]): Either[A, B] = + x match { + case left @ Left(_) => left + case Right(xx) => y match { + case left @ Left(_) => left + case Right(yy) => Right(B.combine(xx, yy)) + } + } + } + implicit def catsStdPartialOrderForEither[A, B](implicit A: PartialOrder[A], B: PartialOrder[B]): PartialOrder[Either[A, B]] = new PartialOrder[Either[A, B]] { def partialCompare(x: Either[A, B], y: Either[A, B]): Double = From 2d3b474079b05003af2e1589c70fcc441d8d21a3 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 17:46:44 -0400 Subject: [PATCH 6/7] One last allocation. --- core/src/main/scala/cats/instances/either.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index f962996cf3..c0763a05e2 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -50,7 +50,7 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { f(b) match { case left @ Left(_) => left.rightCast[C] case Right(Left(b1)) => tailRecM(b1)(f) - case Right(Right(c)) => Right(c) + case Right(right) => right.leftCast[A] } override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = From 24e4de2f849faababbffa10943ef508d2642dd23 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 23 Aug 2016 18:43:21 -0400 Subject: [PATCH 7/7] Fewer allocations (and fix type error). --- core/src/main/scala/cats/instances/either.scala | 10 +++++++--- core/src/main/scala/cats/syntax/either.scala | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index c0763a05e2..64777fd181 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -48,9 +48,13 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { @tailrec def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, C]]): Either[A, C] = f(b) match { - case left @ Left(_) => left.rightCast[C] - case Right(Left(b1)) => tailRecM(b1)(f) - case Right(right) => right.leftCast[A] + case left @ Left(_) => + left.rightCast[C] + case Right(e) => + e match { + case Left(b1) => tailRecM(b1)(f) + case right @ Right(_) => right.leftCast[A] + } } override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 22f4f5681d..c333a00e4a 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -308,7 +308,8 @@ final class RightOps[A, B](val right: Right[A, B]) extends AnyVal { /** Convenience methods to use `Either` syntax inside `Either` syntax definitions. */ private[cats] object EitherUtil { - def leftCast[A, B, C](r: Right[A, B]): Either[C, B] = new RightOps(r).leftCast[C] - - def rightCast[A, B, C](l: Left[A, B]): Either[A, C] = new LeftOps(l).rightCast[C] + def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = + right.asInstanceOf[Either[C, B]] + def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = + left.asInstanceOf[Either[A, C]] }