-
-
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
Generalize ApplyBuilder to Monoidal structures #555
Conversation
ff5b4c4
to
72aa68e
Compare
Current coverage is
|
@julienrf thanks - exciting stuff! It will take me a little while to digest this, but it looks promising. I'm curious what @mpilquist and @non think, as I know they've talked about this recently. |
First of all, thanks @julienrf for getting the ball rolling on this! I do have some design concerns. I think it would be a mistake to introduce a I had been imagining (and actually started sketching) a version where For example: def ap[A, B](fa: F[A], ff: F[A => B]): F[B] =
product(ff, fa).map { case (f, a) => f(a) } Does this make sense? Additionally, I could imagine adding a trait Monoidal[F[_]] extends Functor[F] {
def product(fa: F[A], fb: F[B]): F[(A, B)]
} (Disclaimer: I've spent the weekend getting sick, so I may not be thinking clearly. Please feel free to push back against this design or complicate it with issues I haven't though of.) |
My point is that Again, the purpose of this PR is to make the current “ trait Showable[A] {
def show(a: A): String
} You can implement implicit val monoidalShowable: Monoidal[Showable] =
new Monoidal[Showable] {
def product[A, B](fa: Showable[A], fb: Showable[B]): Showable[(A, B)] =
new Showable[(A, B)] {
def show(ab: (A, B)) = {
val (a, b) = ab
fa.show(a) ++ " " ++ fb.show(b)
}
}
}
implicit val contravariantShowable: Contravariant[Showable] =
new Contravariant[Showable] {
def contramap[A, B](fa: Showable[A], f: B => A): Showable[B] =
new Showable[B] {
def show(b: B) = fa.show(f(b))
}
} Then you can easily build a case class Foo(s: String, i: Int, b: Boolean) implicit val showableFoo: Showable[Foo] =
(
Showable[String] |@|
Showable[Int] |@|
Showable[Boolean] |@|
).contramap(foo => (foo.s, foo.i, foo.b)) A really useful use case for this would be, in circe, to build JSON codecs for a case class from the JSON codecs of each field of the case class (codecs are invariant structures). |
On the other hand, note that my PR changed |
@@ -45,6 +37,9 @@ trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => | |||
def F: Apply[F] = self | |||
def G: Apply[G] = GG | |||
} | |||
|
|||
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ap(fb)(map(fa)(a => b => (a, b))) |
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.
Note that I’m not actually overriding this method, just implementing it, but simulacrum does not accept my code if I omit the override
qualifier.
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.
Would you be willing to reverse this? I.e. leave product
abstract, and implement ap
in terms of product
and map
? I think the implementation will end up being a bit more efficient.
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.
Ok but then I have to update every place in the code where we create instances of Apply
to implement product
instead of ap
. Are you ok with that?
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.
Oh, and we have another problem: Applicative
implements map
in terms of ap
, so if I implement ap
in terms of map
we hit an infinite recursion.
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.
I'm leaving a comment on the overall issue which will (hopefully) clear up where I see this going.
Aha, I see. OK, that totally makes sense. As long as |
@@ -887,7 +887,7 @@ trait StreamingInstances extends StreamingInstances1 { | |||
def combine[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = | |||
xs concat ys | |||
|
|||
override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z): Streaming[Z] = | |||
override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z)(implicit ev: Monoidal[Streaming]): Streaming[Z] = |
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.
Why was this change necessary? If you have a Streaming
in hand you can easily do map
or flatMap
without needing a Monoidal
instance.
So yeah, everything seems fine except a lot of the added |
What is the Monoidal laws? Maybe can't define laws only Monoidal. I think
and |
@xuwei-k Right. If I wonder if we should add |
This parameter is needed just because in all these places we override My reasoning for pulling up Maybe we could find a better design. I need to think a bit about that. |
9839dfa
to
c102c40
Compare
So first of all thanks for bearing with me on this. It's not always obvious how much context one is carrying around in their head until one has to explain it. One thing we've talked about in Gitter (and agreed on) was that Cats should move away from putting implementations in the default type class traits. Particularly, we wanted to move await from designs which allow one to implement Concretely, for your PR, here is how I see it fitting together:
Does this make sense? If it seems too big, I would be happy to try to get the redesign (away from derived methods) pushed through first, which might make it easier for you to make your changes. Thanks again for your work on this. |
6ecc616
to
5c4776f
Compare
Hi, I reworked a bit the PR. Notably, I achieved points 1, 2, 4 and 5 of your previous comment. I kept implementing
Sorry for all the changes… Before I fix the tuts, can you confirm me that the direction this work is going looks good to you? |
Actually let's hold off on (3) for now. I think that should happen in another issue/PR. This looks great! In the long run I'd like to try to deprecate the apply builder syntax (i.e. I'm interested to see what everyone else (particularly @mpilquist) thinks of this. |
5c4776f
to
c4606c4
Compare
c4606c4
to
00e5ab4
Compare
|
||
import cats.Monoidal | ||
|
||
trait MonoidalLaws[F[_]] { |
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.
Can we define the left/right identity and associativity laws here, but instead of returning IsEq[X]
, return a tuple, because the first and second elements will have different but isomorphic types? For example, the left identity law would return a (F[(Unit, A)], F[A])
. This stresses that this type class is not lawless, even if we need some type of functor in order to do the equality check for most F
s.
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.
We'd then bind these laws in InvariantTests
, using the Invariant[F]
instance.
Hi, I decided not to include So, here is a |
f1a2808
to
ecd307e
Compare
@julienrf sorry for the lack of chatter - I'm taking a closer look now I may be missing something but I'm not sure where the trait Semigroupal[F[_]] { // yeah i know
def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}
trait Monoidal[F[_]] extends Semigroupal[F] {
def pure[A](a: A): F[A]
} The Also, sorry again, seem to be merge conflicts now. |
Hi, your idea totally makes sense. The issue I mentionned above with So, if I find some more time, it could be interesting to follow your suggestion and rename the |
Right, so some data types would only be able to define If you are busy these next couple weeks, perhaps we can resolve the merge conflicts and see if we can push what you have here, and then someone else can take over the other work. What are people's thoughts on that? |
OK, I just updated my PR to master and resolved the conflicts. |
18dfb2e
to
3058538
Compare
3058538
to
58b4319
Compare
Pinging @mpilquist and @non - do we want to merge this in the interim and add the version with |
e362fc9
to
9e84c04
Compare
Hi, keeping my branch free of conflicts is a tedious job, if you are ok with that let’s merge this work as it is and we will improve it later by:
I can create the ticket to track progress on this path. |
I think it's a shame we have to duplicate the |
Sorry, I haven't had time to look in detail but I agree that we should merge this and address any issues subsequently.
|
Alright, I'm going to merge this in the interest of getting something up there, and let's address improvements moving forward. Thanks so much @julienrf , sorry it took a while! Also, will leave this here for posterity https://gitter.im/non/cats?at=5669e953de5536717680f8b0 |
Generalize ApplyBuilder to Monoidal structures
yay |
- Reverts many changes introduced by typelevel#555 - Changes `cats.syntax.all._` to `import cats.syntax.monoidal._` in doc
- Reverts many changes introduced by typelevel#555 - Changes `cats.syntax.all._` to `import cats.syntax.monoidal._` in doc
- Remove all equivalent definition of `product` introduced by typelevel#555 - Changes `cats.syntax.all._` to `import cats.syntax.monoidal._` in doc - Minor type argument spacing
I’d like to get your feedback on this work in progress. Here is my plan:
Monoidal
type class ;ApplySyntax
to monoidal structures ;imap
andcontramap
methods toMonoidalBuilder
(that’s why this PR is actually useful) ;The motivation is to be able to express function application (for any arity) within a context, like we can currently do with
Apply
, but with contravariant (e.g. printers) and invariant (e.g. codecs) structures.