diff --git a/core/src/main/scala/cats/data/Op.scala b/core/src/main/scala/cats/data/Op.scala new file mode 100644 index 0000000000..e93d3be648 --- /dev/null +++ b/core/src/main/scala/cats/data/Op.scala @@ -0,0 +1,50 @@ +package cats +package data + +import cats.arrow._ + +/** + * The dual category of some other category, `Arr`. + */ +final case class Op[Arr[_, _], A, B](run: Arr[B, A]) { + def compose[Z](op: Op[Arr, Z, A])(implicit Arr: Compose[Arr]): Op[Arr, Z, B] = + Op(Arr.compose(op.run, run)) + + def eqv(op: Op[Arr, A, B])(implicit Arr: Eq[Arr[B, A]]): Boolean = + Arr.eqv(run, op.run) +} + +object Op extends OpInstances + +private[data] sealed abstract class OpInstances extends OpInstances0 { + implicit def catsDataCategoryForOp[Arr[_, _]](implicit ArrC: Category[Arr]): Category[Op[Arr, ?, ?]] = + new OpCategory[Arr] { def Arr: Category[Arr] = ArrC } + + implicit def catsKernelEqForOp[Arr[_, _], A, B](implicit ArrEq: Eq[Arr[B, A]]): Eq[Op[Arr, A, B]] = + new OpEq[Arr, A, B] { def Arr: Eq[Arr[B, A]] = ArrEq } +} + +private[data] sealed abstract class OpInstances0 { + implicit def catsDataComposeForOp[Arr[_, _]](implicit ArrC: Compose[Arr]): Compose[Op[Arr, ?, ?]] = + new OpCompose[Arr] { def Arr: Compose[Arr] = ArrC } +} + +private[data] trait OpCategory[Arr[_, _]] extends Category[Op[Arr, ?, ?]] with OpCompose[Arr] { + implicit def Arr: Category[Arr] + + override def id[A]: Op[Arr, A, A] = Op(Arr.id) +} + +private[data] trait OpCompose[Arr[_, _]] extends Compose[Op[Arr, ?, ?]] { + implicit def Arr: Compose[Arr] + + def compose[A, B, C](f: Op[Arr, B, C], g: Op[Arr, A, B]): Op[Arr, A, C] = + f.compose(g) +} + +private[data] trait OpEq[Arr[_, _], A, B] extends Eq[Op[Arr, A, B]] { + implicit def Arr: Eq[Arr[B, A]] + + def eqv(f: Op[Arr, A, B], g: Op[Arr, A, B]): Boolean = + f.eqv(g) +} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 63f7df2571..c3675b7e4e 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -240,6 +240,12 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawArbitraryForCokleisliId[A: Arbitrary: Cogen, B: Arbitrary]: Arbitrary[Cokleisli[Id, A, B]] = catsLawsArbitraryForCokleisli[Id, A, B] + implicit def catsLawsArbitraryForOp[Arr[_, _], A, B](implicit Arr: Arbitrary[Arr[B, A]]): Arbitrary[Op[Arr, A, B]] = + Arbitrary(Arr.arbitrary.map(Op(_))) + + implicit def catsLawsCogenForOp[Arr[_, _], A, B](implicit Arr: Cogen[Arr[B, A]]): Cogen[Op[Arr, A, B]] = + Arr.contramap(_.run) + implicit def catsLawsArbitraryForIRWST[F[_]: Applicative, E, L, SA, SB, A](implicit F: Arbitrary[(E, SA) => F[(L, SB, A)]]): Arbitrary[IndexedReaderWriterStateT[F, E, L, SA, SB, A]] = Arbitrary(F.arbitrary.map(IndexedReaderWriterStateT(_))) diff --git a/tests/src/test/scala/cats/tests/OpSuite.scala b/tests/src/test/scala/cats/tests/OpSuite.scala new file mode 100644 index 0000000000..0b2e4ed358 --- /dev/null +++ b/tests/src/test/scala/cats/tests/OpSuite.scala @@ -0,0 +1,37 @@ +package cats +package tests + +import cats.arrow._ +import cats.data.{Kleisli, Op} +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ +import cats.kernel.laws.discipline.EqTests + +class OpSuite extends CatsSuite { + { + implicit val catsKernelEqForOp = Op.catsKernelEqForOp[Function1, Char, Int] + checkAll("Op[Function1, Char, Int]", EqTests[Op[Function1, Char, Int]].eqv) + checkAll("Eq[Op[Function1, Char, Int]]", SerializableTests.serializable(Eq[Op[Function1, Char, Int]])) + } + + { + implicit val catsDataCategoryForOp = Op.catsDataCategoryForOp[Function1] + checkAll("Op[Function1, Char, Int]", CategoryTests[Op[Function1, ?, ?]].category[Char, Int, Char, Int]) + checkAll("Category[Op[Function1, ?, ?]]", SerializableTests.serializable(Category[Op[Function1, ?, ?]])) + } + + /** + * Testing that implicit resolution works. If it compiles, the "test" passes. + */ + object ImplicitResolution { + // Arr is Function1 + Category[Op[Function1, ?, ?]] + Compose[Op[Function1, ?, ?]] + Eq[Op[Function1, Char, Int]] + + // Arr is Kleisli[Option, ?, ?] + Category[Op[Kleisli[Option, ?, ?], ?, ?]] + Compose[Op[Kleisli[Option, ?, ?], ?, ?]] + } +}