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 |
There was a problem hiding this comment.
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.
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.
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.
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.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 Fs.
There was a problem hiding this comment.
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:
Monoidaltype class ;ApplySyntaxto monoidal structures ;imapandcontramapmethods 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.