-
-
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 all 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 Any with 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 |
---|---|---|
|
@@ -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,18 @@ | ||
package cats | ||
package syntax | ||
|
||
trait BifoldableSyntax { | ||
implicit def bifoldableSyntax[F[_, _]: Bifoldable, A, B](fab: F[A, B]): BifoldableOps[F, A, B] = | ||
new BifoldableOps[F, A, B](fab) | ||
} | ||
|
||
final class BifoldableOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifoldable[F]) { | ||
def bifoldLeft[C](c: C)(f: (C, A) => C, g: (C, B) => C): C = | ||
F.bifoldLeft(fab, c)(f, g) | ||
|
||
def bifoldRight[C](c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = | ||
F.bifoldRight(fab, c)(f, g) | ||
|
||
def bifoldMap[C](f: A => C, g: B => C)(implicit C: Monoid[C]): C = | ||
F.bifoldMap(fab)(f, g) | ||
} |
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.
It looks like we are combining the
Bifunctor
andBifoldable
instances in some places but not others. I guess if we are going to go forward with #800 then they will eventually be combined anyway. Was there any particular reason behind when they've been combined and when they haven't?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.
The
Bifunctor[Xor]
instance was there prior to this PR. It wasn't in other places so I figured I would do those separately, hence #867. However because it looks like we have a use case for #800 I may abandon #867 and addBitraverse
once this is OK'ed and merged :-)