-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Bifoldable, fixes #94 #864
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package cats | ||
|
||
/** | ||
* A type class abstracting over types that give rise to two independent [[cats.Foldable]]s. | ||
*/ | ||
trait Bifoldable[F[_, _]] extends Serializable { self => | ||
/** Collapse the structure with a left-associative function */ | ||
def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C | ||
|
||
/** Collapse the structure with a right-associative function */ | ||
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] | ||
|
||
/** Collapse the structure by mapping each element to an element of a type that has a [[cats.Monoid]] */ | ||
def bifoldMap[A, B, C](fab: F[A, B])(f: A => C)(g: B => C)(implicit C: Monoid[C]): C = | ||
bifoldLeft(fab, C.empty)( | ||
(c: C, a: A) => C.combine(c, f(a)), | ||
(c: C, b: B) => C.combine(c, g(b)) | ||
) | ||
|
||
def compose[G[_, _]](implicit ev: Bifoldable[G]): Bifoldable[Lambda[(A, B) => F[G[A, B], G[A, B]]]] = | ||
new CompositeBifoldable[F, G] { | ||
val F = self | ||
val G = ev | ||
} | ||
} | ||
|
||
object Bifoldable { | ||
def apply[F[_, _]](implicit F: Bifoldable[F]): Bifoldable[F] = F | ||
} | ||
|
||
trait CompositeBifoldable[F[_, _], G[_, _]] extends Bifoldable[Lambda[(A, B) => F[G[A, B], G[A, B]]]] { | ||
implicit def F: Bifoldable[F] | ||
implicit def G: Bifoldable[G] | ||
|
||
def bifoldLeft[A, B, C](fab: F[G[A, B], G[A, B]], c: C)(f: (C, A) => C, g: (C, B) => C): C = | ||
F.bifoldLeft(fab, c)( | ||
(c: C, gab: G[A, B]) => G.bifoldLeft(gab, c)(f, g), | ||
(c: C, gab: G[A, B]) => G.bifoldLeft(gab, c)(f, g) | ||
) | ||
|
||
def bifoldRight[A, B, C](fab: F[G[A, B], G[A, B]], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = | ||
F.bifoldRight(fab, c)( | ||
(gab: G[A, B], c: Eval[C]) => G.bifoldRight(gab, c)(f, g), | ||
(gab: G[A, B], c: Eval[C]) => G.bifoldRight(gab, c)(f, g) | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -166,9 +166,19 @@ 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 xorBifunctor: Bifunctor[Xor] = | ||
new Bifunctor[Xor] { | ||
implicit def xorBifunctor: Bifunctor[Xor] with Bifoldable[Xor] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like we are combining the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
new Bifunctor[Xor] with Bifoldable[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) | ||
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] = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,4 @@ trait AllInstances | |
with BigIntInstances | ||
with BigDecimalInstances | ||
with FutureInstances | ||
with TupleInstances |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package cats | ||
package std | ||
|
||
trait TupleInstances extends Tuple2Instances | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this will cause merge conflicts with #867, but I suppose we can just cross that bridge when we get to it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it looks like we're doing #800 I'll be closing that |
||
|
||
sealed trait Tuple2Instances { | ||
implicit val tuple2Bifoldable: Bifoldable[Tuple2] = | ||
new Bifoldable[Tuple2] { | ||
def bifoldLeft[A, B, C](fab: (A, B), c: C)(f: (C, A) => C, g: (C, B) => C): C = | ||
g(f(c, fab._1), fab._2) | ||
|
||
def bifoldRight[A, B, C](fab: (A, B), c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = | ||
g(fab._2, f(fab._1, c)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package cats | ||
package laws | ||
|
||
trait BifoldableLaws[F[_, _]] { | ||
implicit def F: Bifoldable[F] | ||
|
||
def bifoldLeftConsistentWithBifoldMap[A, B, C](fab: F[A, B], f: A => C, g: B => C)(implicit C: Monoid[C]): IsEq[C] = { | ||
val expected = F.bifoldLeft(fab, C.empty)( | ||
(c: C, a: A) => C.combine(c, f(a)), | ||
(c: C, b: B) => C.combine(c, g(b)) | ||
) | ||
expected <-> F.bifoldMap(fab)(f)(g) | ||
} | ||
|
||
def bifoldRightConsistentWithBifoldMap[A, B, C](fab: F[A, B], f: A => C, g: B => C)(implicit C: Monoid[C]): IsEq[C] = { | ||
val expected = F.bifoldRight(fab, Later(C.empty))( | ||
(a: A, ec: Eval[C]) => ec.map(c => C.combine(f(a), c)), | ||
(b: B, ec: Eval[C]) => ec.map(c => C.combine(g(b), c)) | ||
) | ||
expected.value <-> F.bifoldMap(fab)(f)(g) | ||
} | ||
} | ||
|
||
object BifoldableLaws { | ||
def apply[F[_, _]](implicit ev: Bifoldable[F]): BifoldableLaws[F] = | ||
new BifoldableLaws[F] { | ||
def F: Bifoldable[F] = ev | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package cats | ||
package laws | ||
package discipline | ||
|
||
import org.scalacheck.Arbitrary | ||
import org.scalacheck.Prop._ | ||
import org.typelevel.discipline.Laws | ||
|
||
trait BifoldableTests[F[_, _]] extends Laws { | ||
def laws: BifoldableLaws[F] | ||
|
||
def bifoldable[A: Arbitrary, B: Arbitrary, C: Arbitrary: Monoid: Eq](implicit | ||
ArbFAB: Arbitrary[F[A, B]] | ||
): RuleSet = | ||
new DefaultRuleSet( | ||
name = "bifoldable", | ||
parent = None, | ||
"bifoldLeft consistent with bifoldMap" -> forAll(laws.bifoldLeftConsistentWithBifoldMap[A, B, C] _), | ||
"bifoldRight consistent with bifoldMap" -> forAll(laws.bifoldRightConsistentWithBifoldMap[A, B, C] _) | ||
) | ||
} | ||
|
||
object BifoldableTests { | ||
def apply[F[_, _]: Bifoldable]: BifoldableTests[F] = | ||
new BifoldableTests[F] { def laws: BifoldableLaws[F] = BifoldableLaws[F] } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package cats | ||
package tests | ||
|
||
import cats.data.Xor | ||
import cats.laws.discipline.arbitrary.xorArbitrary | ||
import cats.laws.discipline.eq.tuple2Eq | ||
|
||
class MonadCombineTest extends CatsSuite { | ||
test("separate") { | ||
forAll { (list: List[Xor[Int, String]]) => | ||
val ints = list.collect { case Xor.Left(i) => i } | ||
val strings = list.collect { case Xor.Right(s) => s } | ||
val expected = (ints, strings) | ||
|
||
MonadCombine[List].separate(list) should === (expected) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package cats | ||
package tests | ||
|
||
import cats.laws.discipline.{BifoldableTests, SerializableTests} | ||
|
||
class TupleTests extends CatsSuite { | ||
checkAll("Tuple2", BifoldableTests[Tuple2].bifoldable[Int, Int, Int]) | ||
checkAll("Bifoldable[Tuple2]", SerializableTests.serializable(Bifoldable[Tuple2])) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extends Any with Serializable
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I see why we would want to do that, but it doesn't seem like we do it in other type classes.. do we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simulacrum does it automatically. We probably aren't consistent with the
F[_,_]
type classes though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright I added it for
Bifoldable
, I've added an issue to do it for the others and I'll probably end up doing it in a future PR unless someone beats me to it