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 InvariantSemigroupal and ability to turn Monoidals to Monoids #2088

Merged
merged 24 commits into from
Dec 18, 2017

Conversation

LukaJCB
Copy link
Member

@LukaJCB LukaJCB commented Dec 10, 2017

Adds InvariantSemigroupal as supertype of InvariantMonoidal and just like with Apply and Applicative we have functions that turn invariant and contravariant Monoidal and Semigroupal Functors into Monoids and Semigroups respectively.
I'll try to add tests for these, as well as the ones in Apply and Applicative soon.

@kailuowang kailuowang added this to the 1.0.0 milestone Dec 10, 2017
@codecov-io
Copy link

codecov-io commented Dec 10, 2017

Codecov Report

Merging #2088 into master will increase coverage by 0.03%.
The diff coverage is 88.73%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2088      +/-   ##
==========================================
+ Coverage   94.63%   94.66%   +0.03%     
==========================================
  Files         325      328       +3     
  Lines        5514     5533      +19     
  Branches      221      199      -22     
==========================================
+ Hits         5218     5238      +20     
+ Misses        296      295       -1
Impacted Files Coverage Δ
core/src/main/scala/cats/Semigroupal.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/Apply.scala 75% <ø> (+6.25%) ⬆️
core/src/main/scala/cats/Applicative.scala 95% <0%> (+5%) ⬆️
core/src/main/scala/cats/data/IndexedStateT.scala 89.24% <0%> (-0.12%) ⬇️
...rc/main/scala/cats/instances/partialOrdering.scala 77.77% <100%> (+2.77%) ⬆️
core/src/main/scala/cats/data/Kleisli.scala 97.89% <100%> (ø) ⬆️
.../cats/laws/discipline/InvariantMonoidalTests.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/data/WriterT.scala 93.13% <100%> (ø) ⬆️
...e/src/main/scala/cats/instances/partialOrder.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/data/Tuple2K.scala 95.34% <100%> (ø) ⬆️
... and 27 more

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 02b819c...ff0a355. Read the comment docs.

def G: Apply[G]

def product[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[(A, B)]] =
F.imap(F.product(fa, fb)) { case (ga, gb) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

do you plan to add tests for this instance?

*/
@typeclass trait InvariantSemigroupal[F[_]] extends Semigroupal[F] with Invariant[F] { self =>

def composeApply[G[_]: Apply]: InvariantSemigroupal[λ[α => F[G[α]]]] =
Copy link
Contributor

Choose a reason for hiding this comment

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

Any plan to test cover this one as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

Has this been addressed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep!

}

object InvariantSemigroupal extends SemigroupalArityFunctions {
def semigroup[F[_], A](implicit f: InvariantSemigroupal[F], sg: Semigroup[A]): Semigroup[F[A]] =
Copy link
Contributor

Choose a reason for hiding this comment

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

The diff coverage is a bit low, one thing we could do is to add doctests for this semigroup method and the one in ContravariantSemitgroupal, which could also serve as examples. Not critically important though.

@kailuowang
Copy link
Contributor

kailuowang commented Dec 11, 2017

Overall looking good to me. Could use a couple of more tests for the composed instances to bring up the test coverage.

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 11, 2017

Yeah this is still a WIP, wanted to add a bunch more tests, for Applicative/Apply as well.

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 12, 2017

The more I work with this the more I think Apply and ContravariantSemigroupal should extend InvariantSemigroupal and the same applies to Applicative and ContravariantMonoidal.
It would definitely simplify a lot of the compositional things. :)

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 12, 2017

I moved the semigroup and monoid methods to the type classes instead.
This gives us the benefit, that we can optimize this for any instance.

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 12, 2017

So the version right now works without breaking changes, but it'd probably be cleaner to define InvariantMonoidal in terms of InvariantSemigroupal + def unit: F[Unit] and deriving pure from there.
Another thing that bothers me a bit is that I think every Semigroupal is bound to also be an Invariant Functor, no? Maybe I'm wrong about this, but I don't think it's possible. It would also simplify law testing because we can now imap over the resulting F.

@kailuowang
Copy link
Contributor

kailuowang commented Dec 12, 2017

InvariantMonoidal in terms of InvariantSemigroupal + def unit: F[Unit] and deriving pure from there.

Then Applicative won't be able to inherit from InvariantMonoidal right?

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 12, 2017

Nope, that's no problem, in fact if you look at Applicative right now, it already derives unit from pure:

trait Applicative[F[_]] extends Apply[F] with InvariantMonoidal[F] {
  def pure[A](a: A): F[A]
  def unit: F[Unit] = pure(())
}

https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Applicative.scala#L46

@kailuowang
Copy link
Contributor

hmm, I admit I am a bit confused, in ContravariantMonoidal the unit type is
def unit[A]: F[A]. It might not be able to define another def unit: F[Unit] ?

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 12, 2017

It would have to be changed or renamed, yeah :/

Something like:

trait ContravariantMonoidal[F[_]] extends ContravariantSemigroupal[F] with InvariantMonoidal[F] {
  def conquer[A]: F[A]
  def unit: F[Unit] = conquer[Unit] 
}

Or we just remove it, since you can contramap to any type from an F[Unit] by simply doing val fa: F[A] = F.unit.contramap(_ => ())

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 12, 2017

Another argument I find compelling is that literature always only refers to Semigroupal or Monoidal as Functors. i.e. Lax Monoidal Functor, Semigroupal Endofunctor.
So having a Semigroupal that's not a Functor doesn't seem to make a lot of sense naming wise, I think.

@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 16, 2017

Everything should be addressed :)

ceedubs
ceedubs previously approved these changes Dec 17, 2017
Copy link
Contributor

@ceedubs ceedubs left a comment

Choose a reason for hiding this comment

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

👍 LGTM. Codecov is reporting a drop in coverage which might be worth checking out.

F.imap(F.product(fa, fb)) { case (ga, gb) =>
G.map2(ga, gb)(_ -> _)
} { g: G[(A, B)] =>
(G.map(g)(_._1), G.map(g)(_._2))
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is not tested, we might need to test an instance that is not a covariant.

override implicit def F: InvariantMonoidal[F]
import cats.syntax.semigroupal._
import cats.syntax.invariant._

def invariantMonoidalLeftIdentity[A, B](fa: F[A], b: B): IsEq[F[A]] =
F.pure(b).product(fa).imap(_._2)(a => (b, a)) <-> fa
F.point(b).product(fa).imap(_._2)(a => (b, a)) <-> fa
Copy link
Contributor

Choose a reason for hiding this comment

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

We can express these laws in unit right? like

def invariantMonoidalLeftIdentity[A, B](fa: F[A]): IsEq[F[A]] =
 F.unit.product(fa).imap(_._2)(a => ((), a)) <-> fa		 

Then we can add a point - unit consistency law. WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed!

@@ -136,7 +136,7 @@ private[data] sealed abstract class ConstInstances0 extends ConstInstances1 {

private[data] sealed abstract class ConstInstances1 {
implicit def catsConstInvariantMonoidal[C: Monoid]: InvariantMonoidal[Const[C, ?]] = new InvariantMonoidal[Const[C, ?]] {
def pure[A](a: A): Const[C, A] =
def unit: Const[C, Unit] =
Const.empty
Copy link
Contributor

Choose a reason for hiding this comment

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

This one is not tested. I suspect that the instance was never tested before?

override def unit[A]: IndexedStateT[F, S, S, A] =
IndexedStateT.applyF(G.pure((s: S) => F.unit[(S, A)]))
override def unit: IndexedStateT[F, S, S, Unit] =
IndexedStateT.applyF(G.pure((s: S) => F.trivial[(S, Unit)]))
Copy link
Contributor

@kailuowang kailuowang Dec 18, 2017

Choose a reason for hiding this comment

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

It's reported that this instance is not tested. Shall we add one?

Copy link
Contributor

@kailuowang kailuowang Dec 18, 2017

Choose a reason for hiding this comment

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

the ContravariantMonoidal instance of IndexedStateT requires that F to have both FlatMap and ContravriantMonoidal. We can't think of any practical data types that satisfies that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I incline to remove this instance, WDYT @stephen-lazaro ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, it seems at best awkward and unuseful.

@@ -8,7 +8,7 @@ trait EqInstances {
* Defaults to the trivial equivalence relation
* contracting the type to a point
*/
def unit[A]: Eq[A] = Eq.allEqual
def unit: Eq[Unit] = Eq.allEqual
Copy link
Contributor

Choose a reason for hiding this comment

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

not quite sure I understand why this one is reported as not tested either. Any idea?

kailuowang
kailuowang previously approved these changes Dec 18, 2017
@LukaJCB
Copy link
Member Author

LukaJCB commented Dec 18, 2017

Sorry @kailuowang, needed to add a couple more mima exceptions

kailuowang
kailuowang previously approved these changes Dec 18, 2017
@kailuowang
Copy link
Contributor

close and reopen to trigger build and recheck of conflict

@kailuowang kailuowang merged commit 8d4d0b7 into typelevel:master Dec 18, 2017
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.

9 participants