From 912af10a8ede86f540d312d19cb692e0c2c0b32e Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Sun, 10 Apr 2016 21:46:32 -0500 Subject: [PATCH 01/16] Created Copair typeclass --- core/src/main/scala/cats/Copair.scala | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 core/src/main/scala/cats/Copair.scala diff --git a/core/src/main/scala/cats/Copair.scala b/core/src/main/scala/cats/Copair.scala new file mode 100644 index 0000000000..8923876572 --- /dev/null +++ b/core/src/main/scala/cats/Copair.scala @@ -0,0 +1,33 @@ +package cats + +/** + * Created by jbarber on 4/10/16. + */ +trait Copair[F[_,_]] { + def left[A,B](a: A): F[A,B] + def right[A,B](b: B): F[A,B] + + def fold[A,B,C](f: F[A,B])(fa: A => C, fb: B => C): C + + def isRight[A,B](f: F[A,B]): Boolean = fold(f)(_ => false, _ => true ) + def isLeft[A,B](f: F[A,B]): Boolean = fold(f)(_ => true , _ => false) + + def foreach[A,B](f: F[A,B])(fn: B => Unit): Unit = fold(f)(_ => (), fn) + + def orElse[A, B, BB >: B](f: F[A,B])(fallback: F[A,BB]): F[A,BB] = fold(f)(_ => fallback, right(_)) + + def recover[A, B, BB >: B](f: F[A,B])(pf: PartialFunction[A, BB]): F[A,BB] = fold(f)(pf andThen right, right(_)) + + def recoverWith[A, B, AA >: A, BB >: B](f: F[A,B])(pf: PartialFunction[A, F[AA,BB]]): F[AA,BB] = fold(f)(pf, right(_)) + + def swap[A,B](f: F[A,B]): F[B,A] = fold(f)(right[B, A], left[B, A]) + + def valueOr[A, B, BB >: B](f: F[A,B])(fn: A => BB): BB = fold(f)(fn, identity) + + def forall[A, B](f: F[A,B](fn: B => Boolean): Boolean = fold(f)(_ => true, fn) + + def exists[A, B](f: F[A,B](fn: B => Boolean): Boolean = fold(f)(_ => false, fn) + + def ensure[A, B, AA >: A](f: F[A,B])(onFailure: => AA)(fn: B => Boolean): F[AA,B] = + fold(f)(_ => this, b => if (fn(b)) f else left(onFailure)) +} \ No newline at end of file From 4e66ff226957afcd6e5031a10f5191d4cbba05a6 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Mon, 11 Apr 2016 06:30:18 -0500 Subject: [PATCH 02/16] removed probably bad copair implementations, completed syntax file --- core/src/main/scala/cats/Copair.scala | 18 ++-------- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/copair.scala | 35 +++++++++++++++++++ core/src/main/scala/cats/syntax/package.scala | 1 + 4 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/copair.scala diff --git a/core/src/main/scala/cats/Copair.scala b/core/src/main/scala/cats/Copair.scala index 8923876572..8eed0e8316 100644 --- a/core/src/main/scala/cats/Copair.scala +++ b/core/src/main/scala/cats/Copair.scala @@ -1,8 +1,5 @@ package cats -/** - * Created by jbarber on 4/10/16. - */ trait Copair[F[_,_]] { def left[A,B](a: A): F[A,B] def right[A,B](b: B): F[A,B] @@ -14,20 +11,9 @@ trait Copair[F[_,_]] { def foreach[A,B](f: F[A,B])(fn: B => Unit): Unit = fold(f)(_ => (), fn) - def orElse[A, B, BB >: B](f: F[A,B])(fallback: F[A,BB]): F[A,BB] = fold(f)(_ => fallback, right(_)) - - def recover[A, B, BB >: B](f: F[A,B])(pf: PartialFunction[A, BB]): F[A,BB] = fold(f)(pf andThen right, right(_)) - - def recoverWith[A, B, AA >: A, BB >: B](f: F[A,B])(pf: PartialFunction[A, F[AA,BB]]): F[AA,BB] = fold(f)(pf, right(_)) - def swap[A,B](f: F[A,B]): F[B,A] = fold(f)(right[B, A], left[B, A]) - def valueOr[A, B, BB >: B](f: F[A,B])(fn: A => BB): BB = fold(f)(fn, identity) - - def forall[A, B](f: F[A,B](fn: B => Boolean): Boolean = fold(f)(_ => true, fn) - - def exists[A, B](f: F[A,B](fn: B => Boolean): Boolean = fold(f)(_ => false, fn) + def forall[A, B](f: F[A,B])(fn: B => Boolean): Boolean = fold(f)(_ => true, fn) - def ensure[A, B, AA >: A](f: F[A,B])(onFailure: => AA)(fn: B => Boolean): F[AA,B] = - fold(f)(_ => this, b => if (fn(b)) f else left(onFailure)) + def exists[A, B](f: F[A,B])(fn: B => Boolean): Boolean = fold(f)(_ => false, fn) } \ No newline at end of file diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index bbb196d91b..240ae09e1d 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -39,3 +39,4 @@ trait AllSyntax with ValidatedSyntax with CoproductSyntax with WriterSyntax + with CopairSyntax diff --git a/core/src/main/scala/cats/syntax/copair.scala b/core/src/main/scala/cats/syntax/copair.scala new file mode 100644 index 0000000000..7ddbfe4378 --- /dev/null +++ b/core/src/main/scala/cats/syntax/copair.scala @@ -0,0 +1,35 @@ +package cats +package syntax + +/** + * Created by jbarber on 4/10/16. + */ +trait CopairSyntax { + implicit def copairSyntax[F[_, _]: Copair, A, B](fab: F[A, B]): CopairOps[F, A, B] = + new CopairOps[F, A, B](fab) + + implicit def copairIdSyntax[A](a: A): CopairIdOps[A] = new CopairIdOps[A](a) + +} + +final class CopairOps[F[_,_]: Copair, A, B](pair: F[A,B]) { + val ev = implicitly[Copair[F]] + + def fold[C](fa: A => C, fb: B => C): C = ev.fold(pair)(fa, fb) + def swap: F[B,A] = ev.swap(pair) + + def isRight: Boolean = ev.isRight(pair) + def isLeft: Boolean = ev.isLeft(pair) + + def foreach(fn: B => Unit): Unit = ev.foreach(pair)(fn) + + def forall(fn: B => Boolean): Boolean = ev.forall(pair)(fn) + + def exists(fn: B => Boolean): Boolean = ev.forall(pair)(_ => false, fn) + +} + +final class CopairIdOps[A](a: A) { + def left[F[_,_]: Copair, B]: F[A,B] = implicitly[Copair[F]].left(a) + def right[F[_,_]: Copair, B]: F[B,A] = implicitly[Copair[F]].right(a) +} \ No newline at end of file diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 668c2f7311..26031718d7 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -37,4 +37,5 @@ package object syntax { object xor extends XorSyntax object validated extends ValidatedSyntax object writer extends WriterSyntax + object copair extends CopairSyntax } From 4df21ac394e09140fce05b96b244fe5e66049c64 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Mon, 11 Apr 2016 20:48:07 -0500 Subject: [PATCH 03/16] Created derivation of Bitraverse from copair --- core/src/main/scala/cats/syntax/copair.scala | 44 +++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/syntax/copair.scala b/core/src/main/scala/cats/syntax/copair.scala index 7ddbfe4378..a1cf45083a 100644 --- a/core/src/main/scala/cats/syntax/copair.scala +++ b/core/src/main/scala/cats/syntax/copair.scala @@ -1,6 +1,7 @@ package cats package syntax + /** * Created by jbarber on 4/10/16. */ @@ -10,26 +11,47 @@ trait CopairSyntax { implicit def copairIdSyntax[A](a: A): CopairIdOps[A] = new CopairIdOps[A](a) + implicit def bitraverseFromCopair[F[_,_]](implicit F: Copair[F]): Bitraverse[F] = + new Bitraverse[F] { + def bitraverse[G[_], A, B, C, D](fab: F[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[F[C, D]] = + F.fold(fab)( + l => G.map(f(l))(F.left), + r => G.map(g(r))(F.right) + ) + + def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + F.fold(fab)( + l => f(c,l), + r => g(c,r) + ) + + def bifoldRight[A, B, C](fab: F[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + F.fold(fab)( + l => f(l,c), + r => g(r,c) + ) + } + + } -final class CopairOps[F[_,_]: Copair, A, B](pair: F[A,B]) { - val ev = implicitly[Copair[F]] +final class CopairOps[F[_,_], A, B](pair: F[A,B])(implicit F: Copair[F]) { - def fold[C](fa: A => C, fb: B => C): C = ev.fold(pair)(fa, fb) - def swap: F[B,A] = ev.swap(pair) + def fold[C](fa: A => C, fb: B => C): C = F.fold(pair)(fa, fb) + def swap: F[B,A] = F.swap(pair) - def isRight: Boolean = ev.isRight(pair) - def isLeft: Boolean = ev.isLeft(pair) + def isRight: Boolean = F.isRight(pair) + def isLeft: Boolean = F.isLeft(pair) - def foreach(fn: B => Unit): Unit = ev.foreach(pair)(fn) + def foreach(fn: B => Unit): Unit = F.foreach(pair)(fn) - def forall(fn: B => Boolean): Boolean = ev.forall(pair)(fn) + def forall(fn: B => Boolean): Boolean = F.forall(pair)(fn) - def exists(fn: B => Boolean): Boolean = ev.forall(pair)(_ => false, fn) + def exists(fn: B => Boolean): Boolean = F.exists(pair)(fn) } final class CopairIdOps[A](a: A) { - def left[F[_,_]: Copair, B]: F[A,B] = implicitly[Copair[F]].left(a) - def right[F[_,_]: Copair, B]: F[B,A] = implicitly[Copair[F]].right(a) + def left[F[_,_], B](implicit F: Copair[F]): F[A,B] = F.left(a) + def right[F[_,_], B](implicit F: Copair[F]): F[B,A] = F.right(a) } \ No newline at end of file From 3b570a6f37eaf553cebdc54e8c2917a091c0730e Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Mon, 11 Apr 2016 21:03:41 -0500 Subject: [PATCH 04/16] added copair instances --- core/src/main/scala/cats/data/Validated.scala | 12 ++++++++++++ core/src/main/scala/cats/data/Xor.scala | 12 ++++++++++++ core/src/main/scala/cats/std/either.scala | 13 +++++++++++++ 3 files changed, 37 insertions(+) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 22f97f78a6..60eccb06e4 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -228,6 +228,18 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance def show(f: Validated[A,B]): String = f.show } + implicit def validatedCopair: Copair[Validated] = + new Copair[Validated] { + def fold[A, B, C](f: Validated[A, B])(fa: (A) => C, fb: (B) => C): C = + f match { + case Invalid(a) => fa(a) + case Valid(b) => fb(b) + } + + def left[A, B](a: A): Validated[A, B] = Invalid(a) + def right[A, B](b: B): Validated[A, B] = Valid(b) + } + implicit def validatedBifunctor: Bifunctor[Validated] = new Bifunctor[Validated] { override def bimap[A, B, C, D](fab: Validated[A, B])(f: A => C, g: B => D): Validated[C, D] = fab.bimap(f, g) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 470fa448d4..875a1e31a1 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -168,6 +168,18 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } + implicit def xorCopair: Copair[Xor] = + new Copair[Xor] { + def fold[A, B, C](f: Xor[A, B])(fa: (A) => C, fb: (B) => C): C = + f match { + case Xor.Left(a) => fa(a) + case Xor.Right(b) => fb(b) + } + + def left[A, B](a: A): Xor[A, B] = Xor.Left(a) + def right[A, B](b: B): Xor[A, B] = Xor.Right(b) + } + implicit def xorBifunctor: Bitraverse[Xor] = new Bitraverse[Xor] { def bitraverse[G[_], A, B, C, D](fab: Xor[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Xor[C, D]] = diff --git a/core/src/main/scala/cats/std/either.scala b/core/src/main/scala/cats/std/either.scala index cbabb0d8fa..608fccc86c 100644 --- a/core/src/main/scala/cats/std/either.scala +++ b/core/src/main/scala/cats/std/either.scala @@ -2,6 +2,19 @@ package cats package std trait EitherInstances extends EitherInstances1 { + + implicit def eitherCopair: Copair[Either] = + new Copair[Either] { + def fold[A, B, C](f: Either[A, B])(fa: (A) => C, fb: (B) => C): C = + f match { + case Left(a) => fa(a) + case Right(b) => fb(b) + } + + def left[A, B](a: A): Either[A, B] = Left(a) + def right[A, B](b: B): Either[A, B] = Right(b) + } + implicit val eitherBitraverse: 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]] = From 79917e61f2e6bc26147e52f5617e771241ee24dc Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Mon, 11 Apr 2016 21:04:11 -0500 Subject: [PATCH 05/16] Removed bad top comment --- core/src/main/scala/cats/syntax/copair.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/scala/cats/syntax/copair.scala b/core/src/main/scala/cats/syntax/copair.scala index a1cf45083a..250d5f5892 100644 --- a/core/src/main/scala/cats/syntax/copair.scala +++ b/core/src/main/scala/cats/syntax/copair.scala @@ -1,10 +1,6 @@ package cats package syntax - -/** - * Created by jbarber on 4/10/16. - */ trait CopairSyntax { implicit def copairSyntax[F[_, _]: Copair, A, B](fab: F[A, B]): CopairOps[F, A, B] = new CopairOps[F, A, B](fab) From 2cfb5a7c4459c319da16b465568653f21a59d087 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Tue, 12 Apr 2016 19:54:43 -0500 Subject: [PATCH 06/16] Replaced Bitraverse instances in either/Xor/Validated with Copair --- core/src/main/scala/cats/Copair.scala | 20 +++++++++++++- core/src/main/scala/cats/data/Validated.scala | 9 +------ core/src/main/scala/cats/data/Xor.scala | 24 ++--------------- core/src/main/scala/cats/std/either.scala | 23 ++-------------- core/src/main/scala/cats/syntax/copair.scala | 26 ++----------------- 5 files changed, 26 insertions(+), 76 deletions(-) diff --git a/core/src/main/scala/cats/Copair.scala b/core/src/main/scala/cats/Copair.scala index 8eed0e8316..1f933fb57b 100644 --- a/core/src/main/scala/cats/Copair.scala +++ b/core/src/main/scala/cats/Copair.scala @@ -1,6 +1,6 @@ package cats -trait Copair[F[_,_]] { +trait Copair[F[_,_]] extends Bitraverse[F] { def left[A,B](a: A): F[A,B] def right[A,B](b: B): F[A,B] @@ -16,4 +16,22 @@ trait Copair[F[_,_]] { def forall[A, B](f: F[A,B])(fn: B => Boolean): Boolean = fold(f)(_ => true, fn) def exists[A, B](f: F[A,B])(fn: B => Boolean): Boolean = fold(f)(_ => false, fn) + + def bitraverse[G[_], A, B, C, D](fab: F[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[F[C, D]] = + fold(fab)( + l => G.map(f(l))(left), + r => G.map(g(r))(right) + ) + + def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + fold(fab)( + l => f(c,l), + r => g(c,r) + ) + + def bifoldRight[A, B, C](fab: F[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + fold(fab)( + l => f(l,c), + r => g(r,c) + ) } \ No newline at end of file diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 60eccb06e4..2965cb45f8 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -2,7 +2,6 @@ package cats package data import cats.data.Validated.{Invalid, Valid} -import cats.functor.Bifunctor import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} @@ -228,7 +227,7 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance def show(f: Validated[A,B]): String = f.show } - implicit def validatedCopair: Copair[Validated] = + implicit def validatedBifunctor: Copair[Validated] = new Copair[Validated] { def fold[A, B, C](f: Validated[A, B])(fa: (A) => C, fb: (B) => C): C = f match { @@ -240,12 +239,6 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance def right[A, B](b: B): Validated[A, B] = Valid(b) } - implicit def validatedBifunctor: Bifunctor[Validated] = - new Bifunctor[Validated] { - override def bimap[A, B, C, D](fab: Validated[A, B])(f: A => C, g: B => D): Validated[C, D] = fab.bimap(f, g) - override def leftMap[A, B, C](fab: Validated[A, B])(f: A => C): Validated[C, B] = fab.leftMap(f) - } - implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] = new Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] { def traverse[F[_]: Applicative, A, B](fa: Validated[E,A])(f: A => F[B]): F[Validated[E,B]] = diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 875a1e31a1..f757f05f12 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -168,7 +168,7 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } - implicit def xorCopair: Copair[Xor] = + implicit def xorBifunctor: Copair[Xor] = new Copair[Xor] { def fold[A, B, C](f: Xor[A, B])(fa: (A) => C, fb: (B) => C): C = f match { @@ -180,27 +180,6 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { def right[A, B](b: B): Xor[A, B] = Xor.Right(b) } - implicit def xorBifunctor: Bitraverse[Xor] = - new Bitraverse[Xor] { - def bitraverse[G[_], A, B, C, D](fab: Xor[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Xor[C, D]] = - fab match { - case Xor.Left(a) => G.map(f(a))(Xor.left) - case Xor.Right(b) => G.map(g(b))(Xor.right) - } - - def bifoldLeft[A, B, C](fab: Xor[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = - fab match { - case Xor.Left(a) => f(c, a) - case Xor.Right(b) => g(c, b) - } - - def bifoldRight[A, B, C](fab: Xor[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = - fab match { - case Xor.Left(a) => f(a, c) - case Xor.Right(b) => g(b, c) - } - } - implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor[A, ?], A] = new Traverse[A Xor ?] with MonadError[Xor[A, ?], A] { def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f) @@ -237,6 +216,7 @@ private[data] sealed abstract class XorInstances1 extends XorInstances2 { } private[data] sealed abstract class XorInstances2 { + implicit def xorEq[A: Eq, B: Eq]: Eq[A Xor B] = new Eq[A Xor B] { def eqv(x: A Xor B, y: A Xor B): Boolean = x === y diff --git a/core/src/main/scala/cats/std/either.scala b/core/src/main/scala/cats/std/either.scala index 608fccc86c..a162e665cf 100644 --- a/core/src/main/scala/cats/std/either.scala +++ b/core/src/main/scala/cats/std/either.scala @@ -3,7 +3,7 @@ package std trait EitherInstances extends EitherInstances1 { - implicit def eitherCopair: Copair[Either] = + implicit val eitherBitraverse: Copair[Either] = new Copair[Either] { def fold[A, B, C](f: Either[A, B])(fa: (A) => C, fb: (B) => C): C = f match { @@ -15,26 +15,6 @@ trait EitherInstances extends EitherInstances1 { def right[A, B](b: B): Either[A, B] = Right(b) } - implicit val eitherBitraverse: 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]] = - fab match { - case Left(a) => G.map(f(a))(Left(_)) - case Right(b) => G.map(g(b))(Right(_)) - } - - def bifoldLeft[A, B, C](fab: Either[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = - fab match { - case Left(a) => f(c, a) - case Right(b) => g(c, b) - } - - def bifoldRight[A, B, C](fab: Either[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = - fab match { - case Left(a) => f(a, c) - case Right(b) => g(b, c) - } - } implicit def eitherInstances[A]: Monad[Either[A, ?]] with Traverse[Either[A, ?]] = new Monad[Either[A, ?]] with Traverse[Either[A, ?]] { @@ -76,6 +56,7 @@ trait EitherInstances extends EitherInstances1 { } private[std] sealed trait EitherInstances1 extends EitherInstances2 { + implicit def eitherPartialOrder[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( diff --git a/core/src/main/scala/cats/syntax/copair.scala b/core/src/main/scala/cats/syntax/copair.scala index 250d5f5892..43d40ee84d 100644 --- a/core/src/main/scala/cats/syntax/copair.scala +++ b/core/src/main/scala/cats/syntax/copair.scala @@ -7,28 +7,6 @@ trait CopairSyntax { implicit def copairIdSyntax[A](a: A): CopairIdOps[A] = new CopairIdOps[A](a) - implicit def bitraverseFromCopair[F[_,_]](implicit F: Copair[F]): Bitraverse[F] = - new Bitraverse[F] { - def bitraverse[G[_], A, B, C, D](fab: F[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[F[C, D]] = - F.fold(fab)( - l => G.map(f(l))(F.left), - r => G.map(g(r))(F.right) - ) - - def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = - F.fold(fab)( - l => f(c,l), - r => g(c,r) - ) - - def bifoldRight[A, B, C](fab: F[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = - F.fold(fab)( - l => f(l,c), - r => g(r,c) - ) - } - - } final class CopairOps[F[_,_], A, B](pair: F[A,B])(implicit F: Copair[F]) { @@ -48,6 +26,6 @@ final class CopairOps[F[_,_], A, B](pair: F[A,B])(implicit F: Copair[F]) { } final class CopairIdOps[A](a: A) { - def left[F[_,_], B](implicit F: Copair[F]): F[A,B] = F.left(a) - def right[F[_,_], B](implicit F: Copair[F]): F[B,A] = F.right(a) + def leftC[F[_,_], B](implicit F: Copair[F]): F[A,B] = F.left(a) + def rightC[F[_,_], B](implicit F: Copair[F]): F[B,A] = F.right(a) } \ No newline at end of file From 26badc4de81ac6019d54d884ec423fa5cbba8963 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Tue, 12 Apr 2016 20:40:01 -0500 Subject: [PATCH 07/16] Added and implemented tests/some laws. --- .../src/main/scala/cats/laws/CopairLaws.scala | 26 ++++++++++ .../cats/laws/discipline/CopairTests.scala | 49 +++++++++++++++++++ .../test/scala/cats/tests/EitherTests.scala | 4 +- .../scala/cats/tests/ValidatedTests.scala | 4 +- .../src/test/scala/cats/tests/XorTests.scala | 4 +- 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/CopairLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CopairTests.scala diff --git a/laws/src/main/scala/cats/laws/CopairLaws.scala b/laws/src/main/scala/cats/laws/CopairLaws.scala new file mode 100644 index 0000000000..0d48f67fae --- /dev/null +++ b/laws/src/main/scala/cats/laws/CopairLaws.scala @@ -0,0 +1,26 @@ +package cats.laws + +import cats.Copair + +trait CopairLaws[F[_,_]] extends BitraverseLaws[F] with BifoldableLaws[F] with BifunctorLaws[F] { + implicit override def F: Copair[F] + + def copairFoldIdentity[A, B](fab: F[A, B]): IsEq[F[A, B]] = + fab <-> F.fold[A,B,F[A,B]](fab)(F.left, F.right) + + def copairSwapIdentity[A,B](fab: F[A,B]): IsEq[F[A,B]] = + fab <-> F.swap(F.swap(fab)) + + def copairLeftAssociativeIdentity[A, B, C](a: A, fa: A => C, fb: B => C): IsEq[C] = + F.fold(F.left(a))(fa, fb) <-> fa(a) + + def copairRightAssociativeIdentity[A, B, C](b: B, fa: A => C, fb: B => C): IsEq[C] = + F.fold(F.right(b))(fa, fb) <-> fb(b) + + +} + +object CopairLaws { + def apply[F[_, _]](implicit ev: Copair[F]): CopairLaws[F] = + new CopairLaws[F] { def F: Copair[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CopairTests.scala b/laws/src/main/scala/cats/laws/discipline/CopairTests.scala new file mode 100644 index 0000000000..3513a37104 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CopairTests.scala @@ -0,0 +1,49 @@ +package cats.laws.discipline + +import cats.{Eq, Monoid, Applicative, Copair} +import cats.laws.CopairLaws +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll + +trait CopairTests[F[_,_]] extends BitraverseTests[F] with BifoldableTests[F] with BifunctorTests[F] { + def laws: CopairLaws[F] + + def copair[G[_], A, B, C, D, E, H](implicit + G: Applicative[G], + C: Monoid[C], + ArbFAB: Arbitrary[F[A, B]], + ArbFAD: Arbitrary[F[A, D]], + ArbGC: Arbitrary[G[C]], + ArbGD: Arbitrary[G[D]], + ArbGE: Arbitrary[G[E]], + ArbGH: Arbitrary[G[H]], + ArbA: Arbitrary[A], + ArbB: Arbitrary[B], + ArbC: Arbitrary[C], + ArbE: Arbitrary[E], + ArbH: Arbitrary[H], + EqFAB: Eq[F[A, B]], + EqFAD: Eq[F[A, D]], + EqFAH: Eq[F[A, H]], + EqFCD: Eq[F[C, D]], + EqFCH: Eq[F[C, H]], + EqGGFEH: Eq[G[G[F[E, H]]]], + EqC: Eq[C] + ): RuleSet = + new RuleSet { + val name = "copair" + val parents = Seq(bitraverse[G,A,B,C,D,E,H], bifoldable[A, B, C], bifunctor[A, B, C, D, E, H]) + val bases = Seq.empty + val props = Seq( + "copair fold identity" -> forAll(laws.copairFoldIdentity[A, B] _), + "copair swap identity" -> forAll(laws.copairSwapIdentity[A,B] _), + "copair left identity" -> forAll(laws.copairLeftAssociativeIdentity[A,B,C] _), + "copair right identity" -> forAll(laws.copairRightAssociativeIdentity[A,B,C] _) + ) + } +} + +object CopairTests { + def apply[F[_, _]: Copair]: CopairTests[F] = + new CopairTests[F] { def laws: CopairLaws[F] = CopairLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index a104d21cbd..be782ad1ef 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{CopairTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq._ import algebra.laws.OrderLaws @@ -18,7 +18,7 @@ class EitherTests extends CatsSuite { checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) - checkAll("Either[?, ?]", BitraverseTests[Either].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Either[?, ?]", CopairTests[Either].copair[Option, Int, Int, Int, String, String, String]) checkAll("Bitraverse[Either]", SerializableTests.serializable(Bitraverse[Either])) val partialOrder = eitherPartialOrder[Int, String] diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index e755b1719d..ed558e4d39 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -3,7 +3,7 @@ package tests import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor, XorT} import cats.data.Validated.{Valid, Invalid} -import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{CopairTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests} import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq.tuple3Eq @@ -16,7 +16,7 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", CartesianTests[Validated[String,?]].cartesian[Int, Int, Int]) checkAll("Cartesian[Validated[String,?]]", SerializableTests.serializable(Cartesian[Validated[String,?]])) - checkAll("Validated[?, ?]", BifunctorTests[Validated].bifunctor[Int, Int, Int, Int, Int, Int]) + checkAll("Validated[?, ?]", CopairTests[Validated].copair[Option, Int, Int, Int, String, String, String]) implicit val eq0 = XorT.xorTEq[Validated[String, ?], String, Int] diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 58d0a90de2..32a6dd6322 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -4,7 +4,7 @@ package tests import cats.data.{NonEmptyList, Xor, XorT} import cats.data.Xor._ import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{CopairTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq.tuple3Eq import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.Arbitrary @@ -54,7 +54,7 @@ class XorTests extends CatsSuite { } yield xor } - checkAll("? Xor ?", BitraverseTests[Xor].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("? Xor ?", CopairTests[Xor].copair[Option, Int, Int, Int, String, String, String]) checkAll("Bitraverse[Xor]", SerializableTests.serializable(Bitraverse[Xor])) test("catchOnly catches matching exceptions") { From 7a67f713c539cf770a0ac10afe538b3134a19b13 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 19:56:35 -0500 Subject: [PATCH 08/16] updated copair laws to use syntax. --- laws/src/main/scala/cats/laws/CopairLaws.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/laws/src/main/scala/cats/laws/CopairLaws.scala b/laws/src/main/scala/cats/laws/CopairLaws.scala index 0d48f67fae..c00048d493 100644 --- a/laws/src/main/scala/cats/laws/CopairLaws.scala +++ b/laws/src/main/scala/cats/laws/CopairLaws.scala @@ -1,23 +1,22 @@ package cats.laws import cats.Copair +import cats.syntax.copair._ trait CopairLaws[F[_,_]] extends BitraverseLaws[F] with BifoldableLaws[F] with BifunctorLaws[F] { implicit override def F: Copair[F] def copairFoldIdentity[A, B](fab: F[A, B]): IsEq[F[A, B]] = - fab <-> F.fold[A,B,F[A,B]](fab)(F.left, F.right) + fab <-> fab.fold(_.leftC[F, B], _.rightC[F, A]) def copairSwapIdentity[A,B](fab: F[A,B]): IsEq[F[A,B]] = - fab <-> F.swap(F.swap(fab)) + fab <-> fab.swap.swap def copairLeftAssociativeIdentity[A, B, C](a: A, fa: A => C, fb: B => C): IsEq[C] = - F.fold(F.left(a))(fa, fb) <-> fa(a) + a.leftC[F, B].fold(fa, fb) <-> fa(a) def copairRightAssociativeIdentity[A, B, C](b: B, fa: A => C, fb: B => C): IsEq[C] = - F.fold(F.right(b))(fa, fb) <-> fb(b) - - + b.rightC[F, A].fold(fa, fb) <-> fb(b) } object CopairLaws { From bf3a946f3aed2cc6c661a139c5aa8d1038dc3857 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 21:07:01 -0500 Subject: [PATCH 09/16] Added more tests --- .../src/main/scala/cats/laws/CopairLaws.scala | 5 +- .../cats/laws/discipline/CopairTests.scala | 4 +- .../test/scala/cats/tests/CopairTests.scala | 53 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/CopairTests.scala diff --git a/laws/src/main/scala/cats/laws/CopairLaws.scala b/laws/src/main/scala/cats/laws/CopairLaws.scala index c00048d493..69288d3cc7 100644 --- a/laws/src/main/scala/cats/laws/CopairLaws.scala +++ b/laws/src/main/scala/cats/laws/CopairLaws.scala @@ -12,11 +12,12 @@ trait CopairLaws[F[_,_]] extends BitraverseLaws[F] with BifoldableLaws[F] with B def copairSwapIdentity[A,B](fab: F[A,B]): IsEq[F[A,B]] = fab <-> fab.swap.swap - def copairLeftAssociativeIdentity[A, B, C](a: A, fa: A => C, fb: B => C): IsEq[C] = + def copairLeftIdentity[A, B, C](a: A, fa: A => C, fb: B => C): IsEq[C] = a.leftC[F, B].fold(fa, fb) <-> fa(a) - def copairRightAssociativeIdentity[A, B, C](b: B, fa: A => C, fb: B => C): IsEq[C] = + def copairRightIdentity[A, B, C](b: B, fa: A => C, fb: B => C): IsEq[C] = b.rightC[F, A].fold(fa, fb) <-> fb(b) + } object CopairLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/CopairTests.scala b/laws/src/main/scala/cats/laws/discipline/CopairTests.scala index 3513a37104..e93feee136 100644 --- a/laws/src/main/scala/cats/laws/discipline/CopairTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CopairTests.scala @@ -37,8 +37,8 @@ trait CopairTests[F[_,_]] extends BitraverseTests[F] with BifoldableTests[F] wit val props = Seq( "copair fold identity" -> forAll(laws.copairFoldIdentity[A, B] _), "copair swap identity" -> forAll(laws.copairSwapIdentity[A,B] _), - "copair left identity" -> forAll(laws.copairLeftAssociativeIdentity[A,B,C] _), - "copair right identity" -> forAll(laws.copairRightAssociativeIdentity[A,B,C] _) + "copair left identity" -> forAll(laws.copairLeftIdentity[A,B,C] _), + "copair right identity" -> forAll(laws.copairRightIdentity[A,B,C] _) ) } } diff --git a/tests/src/test/scala/cats/tests/CopairTests.scala b/tests/src/test/scala/cats/tests/CopairTests.scala new file mode 100644 index 0000000000..ab2be5a438 --- /dev/null +++ b/tests/src/test/scala/cats/tests/CopairTests.scala @@ -0,0 +1,53 @@ +package cats.tests + +import cats.Copair +import cats.data.Xor +import cats.laws.discipline.{CopairTests => CopairLaws} +import cats.laws.discipline.arbitrary._ + +class CopairTests extends CatsSuite { + + checkAll(" ", CopairLaws[Xor].copair[Option, Int, Int, Int, String, String, String]) + + test("Copair for-each performs side-effect") { + + forAll { copair: Xor[Int, String] => + + def copairForeach[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Unit): Unit = f.foreach(fb) + + var sideEffectOccurred = false + copairForeach(copair)(_ => sideEffectOccurred = true) + + sideEffectOccurred should === (copair.isRight) + } + } + + test("Copair for-all") { + forAll { copair: Xor[String, Int] => + + def copairForAll[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Boolean): Boolean = f.forall(fb) + + copairForAll(copair)(_ % 2 == 0) should === (copair.fold(_ => true, _ % 2 == 0)) + } + } + + test("Copair exists") { + forAll { copair: Xor[String, Int] => + + def copairExists[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Boolean): Boolean = f.exists(fb) + + copairExists(copair)(_ % 2 == 0) should === (copair.fold(_ => false, _ % 2 == 0)) + } + } + + test("Copair left/right") { + forAll { copair: Xor[String, Int] => + + def copairIsLeft[F[_,_]: Copair](f: F[_,_]): Boolean = f.isLeft + def copairIsRight[F[_,_]: Copair](f: F[_,_]): Boolean = f.isRight + + copairIsLeft(copair) should === (copair.fold(_ => true, _ => false)) + copairIsRight(copair) should === (copair.fold(_ => false, _ => true)) + } + } +} From 38a624ec73cdeb58a3c06e1bd17fa42cdc3a60d3 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 21:13:20 -0500 Subject: [PATCH 10/16] improved test implementations, added explanation as to why some of the tests are written how they are. --- .../src/test/scala/cats/tests/CopairTests.scala | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CopairTests.scala b/tests/src/test/scala/cats/tests/CopairTests.scala index ab2be5a438..d3f9b077d1 100644 --- a/tests/src/test/scala/cats/tests/CopairTests.scala +++ b/tests/src/test/scala/cats/tests/CopairTests.scala @@ -22,12 +22,21 @@ class CopairTests extends CatsSuite { } } + /** + * The tests defined below have a def that tests the [[Copair]] implementation, + * followed by a comparison to the [[Xor]] implementation. + * + * They should follow this template: + * def copairFoo[F[_,_]: Copair, A, B](f: F[A,B])... + * copairFoo(copair) should === (copair.foo) + */ + test("Copair for-all") { forAll { copair: Xor[String, Int] => def copairForAll[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Boolean): Boolean = f.forall(fb) - copairForAll(copair)(_ % 2 == 0) should === (copair.fold(_ => true, _ % 2 == 0)) + copairForAll(copair)(_ % 2 == 0) should === (copair.forall(_ % 2 == 0)) } } @@ -36,7 +45,7 @@ class CopairTests extends CatsSuite { def copairExists[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Boolean): Boolean = f.exists(fb) - copairExists(copair)(_ % 2 == 0) should === (copair.fold(_ => false, _ % 2 == 0)) + copairExists(copair)(_ % 2 == 0) should === (copair.exists(_ % 2 == 0)) } } @@ -46,8 +55,8 @@ class CopairTests extends CatsSuite { def copairIsLeft[F[_,_]: Copair](f: F[_,_]): Boolean = f.isLeft def copairIsRight[F[_,_]: Copair](f: F[_,_]): Boolean = f.isRight - copairIsLeft(copair) should === (copair.fold(_ => true, _ => false)) - copairIsRight(copair) should === (copair.fold(_ => false, _ => true)) + copairIsLeft(copair) should === (copair.isLeft) + copairIsRight(copair) should === (copair.isRight) } } } From 6c9a88600a90a7997258c2f6eec5aa729f9b10b0 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 21:22:52 -0500 Subject: [PATCH 11/16] improved test implementations, added explanation as to why some of the tests are written how they are, increased test coverage --- .../test/scala/cats/tests/CopairTests.scala | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CopairTests.scala b/tests/src/test/scala/cats/tests/CopairTests.scala index d3f9b077d1..6a8a959fb9 100644 --- a/tests/src/test/scala/cats/tests/CopairTests.scala +++ b/tests/src/test/scala/cats/tests/CopairTests.scala @@ -1,62 +1,72 @@ package cats.tests import cats.Copair -import cats.data.Xor +import cats.data.{Validated, Xor} import cats.laws.discipline.{CopairTests => CopairLaws} import cats.laws.discipline.arbitrary._ +import org.scalacheck.Arbitrary class CopairTests extends CatsSuite { - checkAll(" ", CopairLaws[Xor].copair[Option, Int, Int, Int, String, String, String]) + checkAll("Xor", CopairLaws[Xor].copair[Option, Int, Int, Int, String, String, String]) - test("Copair for-each performs side-effect") { + testCopairs[Xor]("Xor") + testCopairs[Either]("Either") + testCopairs[Validated]("Validated") - forAll { copair: Xor[Int, String] => + def testCopairs[F[_,_]: Copair](ofType: String)(implicit arb: Arbitrary[F[String, Int]]): Unit = { - def copairForeach[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Unit): Unit = f.foreach(fb) + test(s"$ofType Copair for-each performs side-effect") { - var sideEffectOccurred = false - copairForeach(copair)(_ => sideEffectOccurred = true) + forAll { copair: F[String, Int] => - sideEffectOccurred should === (copair.isRight) + def copairForeach[A, B](f: F[A,B])(fb: B => Unit): Unit = f.foreach(fb) + + var sideEffectOccurred = false + copairForeach(copair)(_ => sideEffectOccurred = true) + + sideEffectOccurred should === (copair.isRight) + } } - } - /** - * The tests defined below have a def that tests the [[Copair]] implementation, - * followed by a comparison to the [[Xor]] implementation. - * - * They should follow this template: - * def copairFoo[F[_,_]: Copair, A, B](f: F[A,B])... - * copairFoo(copair) should === (copair.foo) - */ + /** + * The tests defined below have a def that tests the [[Copair]] implementation, + * followed by a comparison to the [[Xor]] implementation. + * + * They should follow this template: + * def copairFoo[A, B](f: F[A,B])... + * copairFoo(copair) should === (copair.foo) + */ - test("Copair for-all") { - forAll { copair: Xor[String, Int] => + test(s"$ofType Copair for-all") { + forAll { copair: F[String, Int] => - def copairForAll[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Boolean): Boolean = f.forall(fb) + def copairForAll[A, B](f: F[A, B])(fb: B => Boolean): Boolean = f.forall(fb) - copairForAll(copair)(_ % 2 == 0) should === (copair.forall(_ % 2 == 0)) + copairForAll(copair)(_ % 2 == 0) should ===(copair.forall(_ % 2 == 0)) + } } - } - test("Copair exists") { - forAll { copair: Xor[String, Int] => + test(s"$ofType Copair exists") { + forAll { copair: F[String, Int] => - def copairExists[F[_,_]: Copair, A, B](f: F[A,B])(fb: B => Boolean): Boolean = f.exists(fb) + def copairExists[A, B](f: F[A, B])(fb: B => Boolean): Boolean = f.exists(fb) - copairExists(copair)(_ % 2 == 0) should === (copair.exists(_ % 2 == 0)) + copairExists(copair)(_ % 2 == 0) should ===(copair.exists(_ % 2 == 0)) + } } - } - test("Copair left/right") { - forAll { copair: Xor[String, Int] => + test(s"$ofType Copair left/right") { + forAll { copair: F[String, Int] => - def copairIsLeft[F[_,_]: Copair](f: F[_,_]): Boolean = f.isLeft - def copairIsRight[F[_,_]: Copair](f: F[_,_]): Boolean = f.isRight + def copairIsLeft(f: F[_, _]): Boolean = f.isLeft + def copairIsRight(f: F[_, _]): Boolean = f.isRight - copairIsLeft(copair) should === (copair.isLeft) - copairIsRight(copair) should === (copair.isRight) + copairIsLeft(copair) should ===(copair.isLeft) + copairIsRight(copair) should ===(copair.isRight) + } } + + } } From 1142da2d4747285344b257b4da627b34fa172beb Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 21:26:17 -0500 Subject: [PATCH 12/16] Fixed test naming convention to match the rest. --- .../cats/tests/{CopairTests.scala => CopairTest.scala} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename tests/src/test/scala/cats/tests/{CopairTests.scala => CopairTest.scala} (91%) diff --git a/tests/src/test/scala/cats/tests/CopairTests.scala b/tests/src/test/scala/cats/tests/CopairTest.scala similarity index 91% rename from tests/src/test/scala/cats/tests/CopairTests.scala rename to tests/src/test/scala/cats/tests/CopairTest.scala index 6a8a959fb9..629814ba80 100644 --- a/tests/src/test/scala/cats/tests/CopairTests.scala +++ b/tests/src/test/scala/cats/tests/CopairTest.scala @@ -2,13 +2,13 @@ package cats.tests import cats.Copair import cats.data.{Validated, Xor} -import cats.laws.discipline.{CopairTests => CopairLaws} +import cats.laws.discipline.CopairTests import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary -class CopairTests extends CatsSuite { +class CopairTest extends CatsSuite { - checkAll("Xor", CopairLaws[Xor].copair[Option, Int, Int, Int, String, String, String]) + checkAll("Xor", CopairTests[Xor].copair[Option, Int, Int, Int, String, String, String]) testCopairs[Xor]("Xor") testCopairs[Either]("Either") From add8161d8c9d5cc182a556cbb67b224d6451c636 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 21:29:45 -0500 Subject: [PATCH 13/16] Fix copair test implementations --- .../test/scala/cats/tests/CopairTest.scala | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CopairTest.scala b/tests/src/test/scala/cats/tests/CopairTest.scala index 629814ba80..c2179f2046 100644 --- a/tests/src/test/scala/cats/tests/CopairTest.scala +++ b/tests/src/test/scala/cats/tests/CopairTest.scala @@ -29,41 +29,22 @@ class CopairTest extends CatsSuite { } } - /** - * The tests defined below have a def that tests the [[Copair]] implementation, - * followed by a comparison to the [[Xor]] implementation. - * - * They should follow this template: - * def copairFoo[A, B](f: F[A,B])... - * copairFoo(copair) should === (copair.foo) - */ - test(s"$ofType Copair for-all") { forAll { copair: F[String, Int] => - - def copairForAll[A, B](f: F[A, B])(fb: B => Boolean): Boolean = f.forall(fb) - - copairForAll(copair)(_ % 2 == 0) should ===(copair.forall(_ % 2 == 0)) + copair.forall(_ % 2 == 0) should === (copair.fold(_ => true, _ % 2 == 0)) } } test(s"$ofType Copair exists") { forAll { copair: F[String, Int] => - - def copairExists[A, B](f: F[A, B])(fb: B => Boolean): Boolean = f.exists(fb) - - copairExists(copair)(_ % 2 == 0) should ===(copair.exists(_ % 2 == 0)) + copair.exists(_ % 2 == 0) should === (copair.fold(_ => false, _ % 2 == 0)) } } test(s"$ofType Copair left/right") { forAll { copair: F[String, Int] => - - def copairIsLeft(f: F[_, _]): Boolean = f.isLeft - def copairIsRight(f: F[_, _]): Boolean = f.isRight - - copairIsLeft(copair) should ===(copair.isLeft) - copairIsRight(copair) should ===(copair.isRight) + copair.isLeft should === (copair.fold(_ => true, _ => false)) + copair.isRight should === (copair.fold(_ => false, _ => true)) } } From 2a029162aaf54ab0cf4c8a9a0874c027b57751e0 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Wed, 13 Apr 2016 21:31:04 -0500 Subject: [PATCH 14/16] removed old erroneous method --- tests/src/test/scala/cats/tests/CopairTest.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CopairTest.scala b/tests/src/test/scala/cats/tests/CopairTest.scala index c2179f2046..12ec8b2712 100644 --- a/tests/src/test/scala/cats/tests/CopairTest.scala +++ b/tests/src/test/scala/cats/tests/CopairTest.scala @@ -19,11 +19,8 @@ class CopairTest extends CatsSuite { test(s"$ofType Copair for-each performs side-effect") { forAll { copair: F[String, Int] => - - def copairForeach[A, B](f: F[A,B])(fb: B => Unit): Unit = f.foreach(fb) - var sideEffectOccurred = false - copairForeach(copair)(_ => sideEffectOccurred = true) + copair.foreach(_ => sideEffectOccurred = true) sideEffectOccurred should === (copair.isRight) } From 8b4a035209320fececa3e614c2592f242aa70c2c Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Thu, 14 Apr 2016 08:37:13 -0500 Subject: [PATCH 15/16] Added "to" method for converting between copairs, made additional "swap" laws to catch a few cases where somebody could implement swap incorrectly. --- core/src/main/scala/cats/Copair.scala | 2 ++ core/src/main/scala/cats/syntax/copair.scala | 1 + laws/src/main/scala/cats/laws/CopairLaws.scala | 11 ++++++++++- .../main/scala/cats/laws/discipline/CopairTests.scala | 7 +++++-- tests/src/test/scala/cats/tests/CopairTest.scala | 9 +++++++++ 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Copair.scala b/core/src/main/scala/cats/Copair.scala index 1f933fb57b..370bbf2051 100644 --- a/core/src/main/scala/cats/Copair.scala +++ b/core/src/main/scala/cats/Copair.scala @@ -17,6 +17,8 @@ trait Copair[F[_,_]] extends Bitraverse[F] { def exists[A, B](f: F[A,B])(fn: B => Boolean): Boolean = fold(f)(_ => false, fn) + def to[G[_, _], A, B](f: F[A, B])(implicit G: Copair[G]): G[A,B] = fold(f)(G.left, G.right) + def bitraverse[G[_], A, B, C, D](fab: F[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[F[C, D]] = fold(fab)( l => G.map(f(l))(left), diff --git a/core/src/main/scala/cats/syntax/copair.scala b/core/src/main/scala/cats/syntax/copair.scala index 43d40ee84d..0726b3b4e1 100644 --- a/core/src/main/scala/cats/syntax/copair.scala +++ b/core/src/main/scala/cats/syntax/copair.scala @@ -23,6 +23,7 @@ final class CopairOps[F[_,_], A, B](pair: F[A,B])(implicit F: Copair[F]) { def exists(fn: B => Boolean): Boolean = F.exists(pair)(fn) + def to[G[_, _]](implicit G: Copair[G]): G[A,B] = F.to[G,A,B](pair) } final class CopairIdOps[A](a: A) { diff --git a/laws/src/main/scala/cats/laws/CopairLaws.scala b/laws/src/main/scala/cats/laws/CopairLaws.scala index 69288d3cc7..c255eaf72f 100644 --- a/laws/src/main/scala/cats/laws/CopairLaws.scala +++ b/laws/src/main/scala/cats/laws/CopairLaws.scala @@ -9,7 +9,7 @@ trait CopairLaws[F[_,_]] extends BitraverseLaws[F] with BifoldableLaws[F] with B def copairFoldIdentity[A, B](fab: F[A, B]): IsEq[F[A, B]] = fab <-> fab.fold(_.leftC[F, B], _.rightC[F, A]) - def copairSwapIdentity[A,B](fab: F[A,B]): IsEq[F[A,B]] = + def copairDoubleSwapIdentity[A,B](fab: F[A,B]): IsEq[F[A,B]] = fab <-> fab.swap.swap def copairLeftIdentity[A, B, C](a: A, fa: A => C, fb: B => C): IsEq[C] = @@ -18,6 +18,15 @@ trait CopairLaws[F[_,_]] extends BitraverseLaws[F] with BifoldableLaws[F] with B def copairRightIdentity[A, B, C](b: B, fa: A => C, fb: B => C): IsEq[C] = b.rightC[F, A].fold(fa, fb) <-> fb(b) + def copairToIdentity[A, B](fab: F[A,B]): IsEq[F[A,B]] = + fab.to[F] <-> fab + + def copairLeftSwapIdentity[A, B](b: B): IsEq[F[A, B]] = + b.leftC[F, A].swap <-> b.rightC[F, A] + + def copairRightSwapIdentity[A, B](a: A): IsEq[F[A, B]] = + a.rightC[F, B].swap <-> a.leftC[F, B] + } object CopairLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/CopairTests.scala b/laws/src/main/scala/cats/laws/discipline/CopairTests.scala index e93feee136..3710fa822a 100644 --- a/laws/src/main/scala/cats/laws/discipline/CopairTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CopairTests.scala @@ -36,9 +36,12 @@ trait CopairTests[F[_,_]] extends BitraverseTests[F] with BifoldableTests[F] wit val bases = Seq.empty val props = Seq( "copair fold identity" -> forAll(laws.copairFoldIdentity[A, B] _), - "copair swap identity" -> forAll(laws.copairSwapIdentity[A,B] _), + "copair double swap identity" -> forAll(laws.copairDoubleSwapIdentity[A,B] _), + "copair left swap identity" -> forAll(laws.copairLeftSwapIdentity[A, B] _), + "copair right swap identity" -> forAll(laws.copairRightSwapIdentity[A, B] _), "copair left identity" -> forAll(laws.copairLeftIdentity[A,B,C] _), - "copair right identity" -> forAll(laws.copairRightIdentity[A,B,C] _) + "copair right identity" -> forAll(laws.copairRightIdentity[A,B,C] _), + "copair to identity" -> forAll(laws.copairToIdentity[A,B] _) ) } } diff --git a/tests/src/test/scala/cats/tests/CopairTest.scala b/tests/src/test/scala/cats/tests/CopairTest.scala index 12ec8b2712..68c567ec12 100644 --- a/tests/src/test/scala/cats/tests/CopairTest.scala +++ b/tests/src/test/scala/cats/tests/CopairTest.scala @@ -45,6 +45,15 @@ class CopairTest extends CatsSuite { } } + test(s"$ofType Copair to") { + forAll { copair: F[String, Int] => + copair.to[Xor].isLeft should === (copair.isLeft) + copair.to[Xor].isRight should === (copair.isRight) + + val (strFold, intFold): (String => String, Int => String) = (_ => "string", _ => "int") + copair.to[Xor].fold(strFold, intFold) should === (copair.fold(strFold, intFold)) + } + } } } From 33f16d99830d99c0b03d531198a260b6d0e22ce0 Mon Sep 17 00:00:00 2001 From: Jacob Barber Date: Sat, 30 Apr 2016 16:07:17 -0500 Subject: [PATCH 16/16] Pull latest master and resolve conflicts --- tests/src/test/scala/cats/tests/EitherTests.scala | 2 +- tests/src/test/scala/cats/tests/ValidatedTests.scala | 2 +- tests/src/test/scala/cats/tests/XorTests.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index af5261f57d..65c8cb3bd5 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{CopairTests, BitraverseTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{CopairTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} import cats.kernel.laws.OrderLaws class EitherTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 3533edb779..d1c3afbdf9 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -3,7 +3,7 @@ package tests import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor, XorT} import cats.data.Validated.{Valid, Invalid} -import cats.laws.discipline.{CopairTests, BitraverseTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{CopairTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests} import org.scalacheck.Arbitrary._ import cats.laws.discipline.{SemigroupKTests} import cats.laws.discipline.arbitrary._ diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 309f9177d3..55630c0765 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -5,7 +5,7 @@ import cats.data.{NonEmptyList, Xor, XorT} import cats.data.Xor._ import cats.laws.discipline.{SemigroupKTests} import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{CopairTests, BitraverseTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{CopairTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} import cats.kernel.laws.{GroupLaws, OrderLaws} import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._