Skip to content
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 Invariant instances for kernel type classes #1914

Merged
merged 13 commits into from
Oct 17, 2017

Conversation

LukaJCB
Copy link
Member

@LukaJCB LukaJCB commented Sep 15, 2017

Adds InvariantMonoidal to most of the kernel type classes as discussed in #1904.

@kailuowang
Copy link
Contributor

close and reopen to trigger build again (somehow failed on codecov)

@kailuowang kailuowang closed this Sep 15, 2017
@kailuowang kailuowang reopened this Sep 15, 2017
@codecov-io
Copy link

codecov-io commented Sep 15, 2017

Codecov Report

Merging #1914 into master will increase coverage by 0.13%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
core/src/main/scala/cats/instances/invariant.scala 100% <100%> (ø)
laws/src/main/scala/cats/laws/discipline/Eq.scala 94.23% <100%> (+3.32%) ⬆️
core/src/main/scala/cats/Invariant.scala 95.23% <100%> (+23.8%) ⬆️
core/src/main/scala/cats/Semigroupal.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/data/NonEmptyList.scala 100% <0%> (ø) ⬆️
core/src/main/scala/cats/data/NonEmptyVector.scala 100% <0%> (ø) ⬆️
core/src/main/scala/cats/data/Validated.scala 100% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 167d5b4...64cd30d. Read the comment docs.

@LukaJCB
Copy link
Member Author

LukaJCB commented Sep 15, 2017

Should mention that this is an API breaking change, as imports from cats.instances.monoid and cats.instances.semigroup no longer work.

@kailuowang
Copy link
Contributor

kailuowang commented Sep 15, 2017

I probably missed something but I had the impression that we want to include all these instances into the companion objects of Invariant and InvariantMonoidal, so that no imports is needed at all. Is that not possible or having significant cons?

@LukaJCB
Copy link
Member Author

LukaJCB commented Sep 15, 2017

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?

@kailuowang
Copy link
Contributor

If you put them in the companion, they never need to be imported right?

@LukaJCB
Copy link
Member Author

LukaJCB commented Sep 15, 2017

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?
So it'd have be something along the lines of import cats.functor.Invariant._

@kailuowang
Copy link
Contributor

kailuowang commented Sep 15, 2017

@LukaJCB for your example, it should work without import if the Invariant instance for Semigroup is defined in Invariant's companion object. Actually what I said previously isn't accurate, we should put these Invariant and InvariantMonoidal instances into the companion object of Invariant

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
}

@LukaJCB
Copy link
Member Author

LukaJCB commented Sep 15, 2017

Ah okay, I think I've found the issue, we currently have tests, that check whether Invariant, Cartesian and InvariantMonoidal instances can be summoned and if we put it in object Invariant it can't find those Cartesian instances. However, if we put them in object InvariantMonoidal it can't find Invariant or Cartesian instances.
I guess the Cartesian instances aren't that important, but I'm not sure, WDYT?

@kailuowang
Copy link
Contributor

kailuowang commented Sep 15, 2017

ah didn't realize about the Cartesian, Crap, we can't put them in both, we have to put them an invariant object for people to import then. @johnynek WDYT?

@kailuowang kailuowang modified the milestone: 1.0.0-RC1 Sep 19, 2017
@johnynek
Copy link
Contributor

we could have a fallback possibly that looks in the other places.

So if A extends B we could have a low priority implicit that looks in A to provide an implicit of B. That said, that makes everything have a circular dependency.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member Author

@LukaJCB LukaJCB Sep 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like these are all InvariantMonoidal not just Invariant. Should we put them in a different trait name?

@LukaJCB
Copy link
Member Author

LukaJCB commented Sep 20, 2017

I've added an override for combineAllOption to all the instances :)

Edit: seems like the overriden combineAllOption are untested though

implicit val arbCommutativeGroupInt: Arbitrary[CommutativeGroup[Int]] =
Arbitrary(genCommutativeGroupInt)

test("Semigroup combineAllOption after imap with id is consistent"){
Copy link
Contributor

@kailuowang kailuowang Sep 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't know we had such a convention, good to know :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member Author

@LukaJCB LukaJCB Sep 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could do that, but I didn't think the pure ones were actually lawful instances.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be curious to find out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I think we should get rid of all Invariant Monoidal except for Semigroup and Commutative Semigroup and Turn the others into Invariant instances

Copy link
Contributor

@kailuowang kailuowang Sep 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 BTW, for Invariant instances, they can be moved the companion object of Invariant right?

@LukaJCB
Copy link
Member Author

LukaJCB commented Sep 28, 2017

To summarize the breaking changes introduced in this PR for later use:

  • The InvariantMonoidal instance from cats.instances.semigroup was moved to cats.instances.invariant
  • The InvariantMonoidal instance from cats.instances.monoid was removed as it was not lawful. In its place there's an Invariant instance, located in object Invariant.

kailuowang
kailuowang previously approved these changes Sep 28, 2017
@kailuowang
Copy link
Contributor

do you mind also update the doc of InvariantMonoidal https://github.com/typelevel/cats/blob/master/docs/src/main/tut/typeclasses/invariantmonoidal.md#L20
Line 20 mentioned Monoid as an InvariantMonoidal

kailuowang
kailuowang previously approved these changes Oct 3, 2017
kailuowang
kailuowang previously approved these changes Oct 4, 2017
@kailuowang
Copy link
Contributor

@johnynek got any more feedback for this PR?

@kailuowang kailuowang added this to the 1.0.0-RC1 milestone Oct 13, 2017
kailuowang
kailuowang previously approved these changes Oct 16, 2017
@kailuowang kailuowang mentioned this pull request Oct 16, 2017
12 tasks
@@ -6,8 +6,6 @@ package tests
class MonoidTests extends CatsSuite {
{
Invariant[Monoid]
Semigroupal[Monoid]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we lose this? I don't see it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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! :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that too. yeah, what broke the law was the pure method added in InvariantMonoidal

@johnynek
Copy link
Contributor

👍

@LukaJCB LukaJCB merged commit 8d7b394 into typelevel:master Oct 17, 2017
@LukaJCB LukaJCB deleted the add-invariant-instances branch October 17, 2017 23:57
@johnynek
Copy link
Contributor

@LukaJCB Thank you so much for carrying this over the line!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants