Skip to content

Commit

Permalink
Merge pull request #559 from agenovese/Issue557
Browse files Browse the repository at this point in the history
Introduce Bifunctor Laws
  • Loading branch information
fthomas committed Oct 4, 2015
2 parents c036249 + 786d532 commit 3565ed4
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 6 deletions.
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/data/Ior.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import cats.functor.Bifunctor

/** Represents a right-biased disjunction that is either an `A`, or a `B`, or both an `A` and a `B`.
*
* An instance of `A [[Ior]] B` is one of:
Expand Down Expand Up @@ -142,6 +144,11 @@ sealed abstract class IorInstances extends IorInstances0 {
def pure[B](b: B): A Ior B = Ior.right(b)
def flatMap[B, C](fa: A Ior B)(f: B => A Ior C): A Ior C = fa.flatMap(f)
}

implicit def iorBifunctor: Bifunctor[Ior] =
new Bifunctor[Ior] {
override def bimap[A, B, C, D](fab: A Ior B)(f: A => C, g: B => D): C Ior D = fab.bimap(f, g)
}
}

sealed abstract class IorInstances0 {
Expand Down
11 changes: 9 additions & 2 deletions core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package cats
package data

import cats.data.Validated.{Invalid, Valid}
import cats.functor.Bifunctor

import scala.reflect.ClassTag
import cats.data.Validated.{Valid, Invalid}
import scala.util.{Success, Failure, Try}
import scala.util.{Failure, Success, Try}

sealed abstract class Validated[+E, +A] extends Product with Serializable {

Expand Down Expand Up @@ -172,6 +174,11 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 {
def show(f: Validated[A,B]): String = f.show
}

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)
}

implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with Applicative[Validated[E, ?]] =
new Traverse[Validated[E, ?]] with Applicative[Validated[E,?]] {
def traverse[F[_]: Applicative, A, B](fa: Validated[E,A])(f: A => F[B]): F[Validated[E,B]] =
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/data/Xor.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import cats.functor.Bifunctor

import scala.reflect.ClassTag
import scala.util.{Failure, Success, Try}

Expand Down Expand Up @@ -162,6 +164,12 @@ sealed abstract class XorInstances extends XorInstances1 {
def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y
}

implicit def xorBifunctor: Bifunctor[Xor] =
new Bifunctor[Xor] {
override def bimap[A, B, C, D](fab: A Xor B)(f: A => C, g: B => D): C Xor D = fab.bimap(f, g)
}


implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ] =
new Traverse[A Xor ?] with MonadError[Xor, A] {
def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f)
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/data/XorT.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import cats.functor.Bifunctor

/**
* Transformer for `Xor`, allowing the effect of an arbitrary type constructor `F` to be combined with the
* fail-fast effect of `Xor`.
Expand Down Expand Up @@ -156,6 +158,13 @@ abstract class XorTInstances extends XorTInstances1 {

implicit def xorTShow[F[_], L, R](implicit sh: Show[F[L Xor R]]): Show[XorT[F, L, R]] =
functor.Contravariant[Show].contramap(sh)(_.value)

implicit def xorTBifunctor[F[_]](implicit F: Functor[F]): Bifunctor[XorT[F, ?, ?]] = {
new Bifunctor[XorT[F, ?, ?]] {
override def bimap[A, B, C, D](fab: XorT[F, A, B])(f: A => C, g: B => D): XorT[F, C, D] = fab.bimap(f, g)
}
}

}

private[data] abstract class XorTInstances1 extends XorTInstances2 {
Expand Down
25 changes: 25 additions & 0 deletions laws/src/main/scala/cats/laws/BifunctorLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cats.laws

import cats.functor.Bifunctor
import cats.syntax.bifunctor._

/**
* Laws that must be obeyed by any `Bifunctor`.
*/
trait BifunctorLaws[F[_, _]] {
implicit def F: Bifunctor[F]

def bifunctorIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] =
fa.bimap(identity, identity) <-> fa

def bifunctorComposition[A, B, C, X, Y, Z](fa: F[A, X], f: A => B, f2: B => C, g: X => Y, g2: Y => Z): IsEq[F[C, Z]] = {
fa.bimap(f, g).bimap(f2, g2) <-> fa.bimap(f andThen f2, g andThen g2)
}
}

object BifunctorLaws {
def apply[F[_,_]](implicit ev: Bifunctor[F]): BifunctorLaws[F] =
new BifunctorLaws[F] {
def F: Bifunctor[F] = ev
}
}
36 changes: 36 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cats.laws.discipline

import cats.Eq
import cats.functor.Bifunctor
import cats.laws.BifunctorLaws
import org.scalacheck.Arbitrary
import org.scalacheck.Prop._
import org.typelevel.discipline.Laws

trait BifunctorTests[F[_, _]] extends Laws {
def laws: BifunctorLaws[F]

def bifunctor[A, A2, A3, B, B2, B3](implicit
ArbFAB: Arbitrary[F[A, B]],
ArbA2: Arbitrary[A => A2],
ArbA3: Arbitrary[A2 => A3],
ArbB2: Arbitrary[B => B2],
ArbB3: Arbitrary[B2 => B3],
EqFAB: Eq[F[A, B]],
EqFCZ: Eq[F[A3, B3]]
): RuleSet = {
new DefaultRuleSet(
name = "Bifunctor",
parent = None,
"Bifunctor Identity" -> forAll(laws.bifunctorIdentity[A, B] _),
"Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _)
)
}
}

object BifunctorTests {
def apply[F[_, _] : Bifunctor]: BifunctorTests[F] =
new BifunctorTests[F] {
def laws: BifunctorLaws[F] = BifunctorLaws[F]
}
}
3 changes: 2 additions & 1 deletion tests/src/test/scala/cats/tests/IorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package tests

import cats.data.{Xor, Ior}
import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests}
import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadTests, SerializableTests}
import cats.laws.discipline.arbitrary._
import org.scalacheck.Arbitrary
import org.scalacheck.Arbitrary._
Expand All @@ -15,6 +15,7 @@ class IorTests extends CatsSuite {

checkAll("Ior[String, Int] with Option", TraverseTests[String Ior ?].traverse[Int, Int, Int, Int, Option, Option])
checkAll("Traverse[String Ior ?]", SerializableTests.serializable(Traverse[String Ior ?]))
checkAll("? Ior ?", BifunctorTests[Ior].bifunctor[Int, Int, Int, String, String, String])

check {
forAll { (i: Int Ior String) =>
Expand Down
3 changes: 2 additions & 1 deletion tests/src/test/scala/cats/tests/ValidatedTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package tests

import cats.data.{NonEmptyList, Validated}
import cats.data.Validated.{Valid, Invalid}
import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests}
import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests}
import org.scalacheck.{Gen, Arbitrary}
import org.scalacheck.Arbitrary._
import org.scalacheck.Prop._
Expand All @@ -14,6 +14,7 @@ import scala.util.Try

class ValidatedTests extends CatsSuite {
checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int])
checkAll("Validated[?, ?]", BifunctorTests[Validated].bifunctor[Int, Int, Int, Int, Int, Int])
checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[String,?]]))

checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String,?]].traverse[Int, Int, Int, Int, Option, Option])
Expand Down
3 changes: 2 additions & 1 deletion tests/src/test/scala/cats/tests/XorTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats.tests

import cats.{Id, MonadError}
import cats.data.{Xor, XorT}
import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests}
import cats.laws.discipline.{BifunctorTests, MonadErrorTests, MonoidKTests, SerializableTests}
import cats.laws.discipline.arbitrary._

import org.scalacheck.Prop.forAll
Expand All @@ -11,6 +11,7 @@ class XorTTests extends CatsSuite {
checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int])
checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int])
checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, ?, ?], String]))
checkAll("XorT[List, ?, ?]", BifunctorTests[XorT[List, ?, ?]].bifunctor[Int, Int, Int, String, String, String])

test("toValidated")(check {
forAll { (xort: XorT[List, String, Int]) =>
Expand Down
5 changes: 4 additions & 1 deletion tests/src/test/scala/cats/tests/XorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package tests
import cats.data.Xor
import cats.data.Xor._
import cats.laws.discipline.arbitrary.xorArbitrary
import cats.laws.discipline.{TraverseTests, MonadErrorTests, SerializableTests}
import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests}
import org.scalacheck.{Arbitrary, Gen}
import org.scalacheck.Prop._
import org.scalacheck.Prop.BooleanOperators
Expand All @@ -21,6 +21,7 @@ class XorTests extends CatsSuite {
checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option])
checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]]))


implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary {
for {
left <- arbitrary[Boolean]
Expand All @@ -29,6 +30,8 @@ class XorTests extends CatsSuite {
} yield xor
}

checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String])

test("fromTryCatch catches matching exceptions") {
assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]])
}
Expand Down

0 comments on commit 3565ed4

Please sign in to comment.