Add Invariant instances for kernel type classes#1914
Conversation
5ab0e76 to
ac4c94f
Compare
|
close and reopen to trigger build again (somehow failed on codecov) |
Codecov Report
@@ Coverage Diff @@
## master #1914 +/- ##
==========================================
+ Coverage 96.08% 96.21% +0.13%
==========================================
Files 273 272 -1
Lines 4541 4627 +86
Branches 119 124 +5
==========================================
+ Hits 4363 4452 +89
+ Misses 178 175 -3
Continue to review full report at Codecov.
|
ac4c94f to
f3752f1
Compare
|
Should mention that this is an API breaking change, as imports from |
|
I probably missed something but I had the impression that we want to include all these instances into the companion objects of |
|
Oh, I may have misunderstood then. The only con, I can think about is that these won't get imported when importing all instances which might be confusing to some? |
|
If you put them in the companion, they never need to be imported right? |
|
They don't? 🤔 Maybe I'm missing something here, but what would happen if I want to do something like: val semiB: Semigroup[B] = Semigroup[A].imap(...)I'd need to import the implicit instance from somewhere, right? |
|
@LukaJCB for your example, it should work without import if the demo, the following code compiles trait TCParent[F[_]]
trait TCChild[F[_]] extends TCParent[F]
trait A[T]
object TCParent {
implicit val tcA: TCChild[A] = null
}
object Test {
implicit class TCParentOps[F[_], T](f: F[T])(implicit tcF: TCParent[F]) {
def e1: String = "bah"
}
implicit class TCOps[F[_], T](f: F[T])(implicit tcF: TCChild[F]) {
def e: String = "bah"
}
val a: A[String] = ???
a.e
a.e1
}
|
|
Ah okay, I think I've found the issue, we currently have tests, that check whether |
|
ah didn't realize about the |
2ab053d to
f3752f1
Compare
|
we could have a fallback possibly that looks in the other places. So if Not really sure that's a great idea. |
| implicit val catsInvariantMonoidalMonoid: InvariantMonoidal[Monoid] = new InvariantMonoidal[Monoid] { | ||
| def product[A, B](fa: Monoid[A], fb: Monoid[B]): Monoid[(A, B)] = new Monoid[(A, B)] { | ||
| val empty = fa.empty -> fb.empty | ||
| def combine(x: (A, B), y: (A, B)): (A, B) = fa.combine(x._1, y._1) -> fb.combine(x._2, y._2) |
There was a problem hiding this comment.
shame this destroys the combineAllOption optimization. Maybe we can add a ticket to address this after we merge.
In algebird, we buffer 1000 items internally, then pass over the block with each of the underlying monoids.
| def pure[A](a: A): CommutativeMonoid[A] = new CommutativeMonoid[A] { | ||
| val empty = a | ||
| def combine(x: A, y: A): A = a | ||
| override def combineAll(as: TraversableOnce[A]): A = a |
There was a problem hiding this comment.
can we only override combineAllOption? If we override only combineAll, then calls to combineAllOption still get the slow path. combineAll calls back to combineAllOption (combineAll should probably be final, actually).
There was a problem hiding this comment.
I did override in all of the instances, but can you comment on how this is useful? I really can't come up with a way 🤔 and therefore can't find a good way to test these
| import cats.kernel._ | ||
| import cats.InvariantMonoidal | ||
|
|
||
| trait InvariantInstances { |
There was a problem hiding this comment.
looks like these are all InvariantMonoidal not just Invariant. Should we put them in a different trait name?
|
I've added an override for Edit: seems like the overriden |
66f8943 to
a85ce5c
Compare
| implicit val arbCommutativeGroupInt: Arbitrary[CommutativeGroup[Int]] = | ||
| Arbitrary(genCommutativeGroupInt) | ||
|
|
||
| test("Semigroup combineAllOption after imap with id is consistent"){ |
There was a problem hiding this comment.
I propose we add this to the Semigroup law as a "Ref" law like these
https://github.com/typelevel/cats/blob/master/laws/src/main/scala/cats/laws/FoldableLaws.scala#L122-L143
There was a problem hiding this comment.
Ah, I didn't know we had such a convention, good to know :)
There was a problem hiding this comment.
We dont' really, but I'd like to establish one, see discussion here. @peterneyens has some interesting ideas too.
| implicit val arbCommutativeGroupInt: Arbitrary[CommutativeGroup[Int]] = | ||
| Arbitrary(genCommutativeGroupInt) | ||
|
|
||
| def combineAllOption_Ref[F[_] : InvariantMonoidal, A: Eq](name: String, a: A)(implicit ev: F[A] <:< Semigroup[A]): Unit = { |
There was a problem hiding this comment.
Hmm, I was suggesting adding this to the SemigroupLaw but I just realized that the consolidation PR #1922 isn't merged yet. Let's wait until that one gets merged, shall we?
There was a problem hiding this comment.
I agree we should wait, however, this one isn't really about combineAllOption for any given semigroup, but for the ones created by InvariantMonoidal[F].pure. SemigroupLaws already has a check for combineAllOption here :)
There was a problem hiding this comment.
ah I see. so, even without this methods and the tests that use it, the combineAllOption should be covered once you used GroupLaws to test the new instances? So for these pure given instance, why not test them with the GroupLaws again?
There was a problem hiding this comment.
I think we could do that, but I didn't think the pure ones were actually lawful instances.
There was a problem hiding this comment.
I'd be curious to find out.
There was a problem hiding this comment.
Okay so the Semigroup instance is not lawful, because of the combineAllOption, as defined here, is not consistent with reduceOption(_ |+| _), because when you pass a collection with only one value reduceOption will only turn that into an Option. One could argue that this doesn't necessarily break the Semigroup laws though.
In addition the Monoid instance fails to fulfil the left and right identity laws.
Also these instances aren't able to abide by the idempotency law.
Does this mean Monoid is not a valid InvariantMonoidal? I'm not sure I know enough about that.
There was a problem hiding this comment.
if the pure instance is not lawful, I am not sure if it's still a InvariantMonoidal, maybe just a Invariant? maybe worthwhile to find out how it's defined in Haskell.
There was a problem hiding this comment.
I was surprised at the pure. I thought these two were only Invariant.
By the way, I think Invariant and Cartesian are the two ways you actually want to compose these things.
There was a problem hiding this comment.
So, I think we should get rid of all Invariant Monoidal except for Semigroup and Commutative Semigroup and Turn the others into Invariant instances
There was a problem hiding this comment.
👍 BTW, for Invariant instances, they can be moved the companion object of Invariant right?
5f9b12a to
29e5322
Compare
|
To summarize the breaking changes introduced in this PR for later use:
|
|
do you mind also update the doc of |
29e5322 to
6ac8aaf
Compare
|
@johnynek got any more feedback for this PR? |
070f620 to
60aa276
Compare
| class MonoidTests extends CatsSuite { | ||
| { | ||
| Invariant[Monoid] | ||
| Semigroupal[Monoid] |
There was a problem hiding this comment.
why did we lose this? I don't see it?
There was a problem hiding this comment.
Good catch, I falsely assumed, that because Monoid wasn't a valid InvariantMonoidal, that it also wasn't a lawful Semigroupal. Turns out, it is, and I added it back! Thanks! :)
There was a problem hiding this comment.
I missed that too. yeah, what broke the law was the pure method added in InvariantMonoidal
|
👍 |
|
@LukaJCB Thank you so much for carrying this over the line! |
Adds
InvariantMonoidalto most of the kernel type classes as discussed in #1904.