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

Syntax for Cartesian operations with tuples #1112

Merged
merged 1 commit into from
Aug 12, 2016

Conversation

DavidGregory084
Copy link
Member

@DavidGregory084 DavidGregory084 commented Jun 10, 2016

Generates

trait TupleAritySyntax {
  private[syntax] final class Tuple1Ops[F[_], A0](t1: Tuple1[F[A0]]) {
    def map[Z](f: (A0) => Z)(implicit functor: Functor[F]): F[Z] = functor.map(t1._1)(f)
    def contramap[Z](f: Z => (A0))(implicit contravariant: Contravariant[F]): F[Z] = contravariant.contramap(t1._1)(f)
    def imap[Z](f: (A0) => Z)(g: Z => (A0))(implicit invariant: Invariant[F]): F[Z] = invariant.imap(t1._1)(f)(g)
  }
  implicit def tuple1Syntax[F[_], A0](t1: Tuple1[F[A0]]): Tuple1Ops[F, A0] = new Tuple1Ops(t1)
  private[syntax] final class Tuple2Ops[F[_], A0, A1](t2: Tuple2[F[A0], F[A1]]) {
    def map2[Z](f: (A0, A1) => Z)(implicit functor: Functor[F], cartesian: Cartesian[F]): F[Z] = Cartesian.map2(t2._1, t2._2)(f)
    def contramap2[Z](f: Z => (A0, A1))(implicit contravariant: Contravariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.contramap2(t2._1, t2._2)(f)
    def imap2[Z](f: (A0, A1) => Z)(g: Z => (A0, A1))(implicit invariant: Invariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.imap2(t2._1, t2._2)(f)(g)
  }
  implicit def tuple2Syntax[F[_], A0, A1](t2: Tuple2[F[A0], F[A1]]): Tuple2Ops[F, A0, A1] = new Tuple2Ops(t2)

  // etc.
}

Things I'm not sure about:

@codecov-io
Copy link

codecov-io commented Jun 11, 2016

Current coverage is 90.58% (diff: 100%)

Merging #1112 into master will increase coverage by 0.02%

@@             master      #1112   diff @@
==========================================
  Files           243        243          
  Lines          3293       3292     -1   
  Methods        3235       3236     +1   
  Messages          0          0          
  Branches         56         53     -3   
==========================================
  Hits           2982       2982          
+ Misses          311        310     -1   
  Partials          0          0          

Sunburst

Powered by Codecov. Last update ce70923...b6bd29a

@yilinwei
Copy link
Contributor

The point if I'm not mistaken is to be able to write (fa, fb).map2 { case (a, b) => ??? } instead of (fa |@| fb) map { case (a, b) => ??? }?

My thoughts are:

  1. This falls under a syntax change, I think that adopting one should mean that we should phase out the second.
  2. Don't feel strongly either way, though I think that the methods should stay the same i.e. map instead of map2

I guess another option is have a toCartesianBuilder generated on the tuple ops as an alias to |@| symbol.

@ceedubs
Copy link
Contributor

ceedubs commented Jun 11, 2016

Thanks @DavidGregory084!

Some perhaps relevant (but rushed because I have to go) notes:

  • CartesianBulider/|@| have historically been used because they work for things like Validated while the tuple approach doesn't. However the recent SI-2712 fix probably means that this doesn't have to be an issue anymore.
  • Off the top of my head, I don't know whether cats has a Functor instances for tuple2, but it would be a reasonable thing to have, and if it were around, map on a tuple2 would become ambiguous. Since these methods aren't the same as the traditional Functor.map I think that they should have different names (whether that's map3 etc or something else).

@DavidGregory084
Copy link
Member Author

Thanks @yilinwei, @ceedubs. I tidied up the naming in this PR to follow @kailuowang's latest changes to disambiguate syntax implicits. I also made a small change to the boilerplate generation to allow multiple blocks to be expanded separately, e.g.

trait TupleCartesianSyntax {
  implicit def catsSyntaxTuple1Cartesian[F[_], A0](t1: Tuple1[F[A0]]): Tuple1CartesianOps[F, A0] = new Tuple1CartesianOps(t1)
  implicit def catsSyntaxTuple2Cartesian[F[_], A0, A1](t2: Tuple2[F[A0], F[A1]]): Tuple2CartesianOps[F, A0, A1] = new Tuple2CartesianOps(t2)
  // etc.
}

private[syntax] final class Tuple1CartesianOps[F[_], A0](t1: Tuple1[F[A0]]) {
  def map[Z](f: (A0) => Z)(implicit functor: Functor[F]): F[Z] = functor.map(t1._1)(f)
  def contramap[Z](f: Z => (A0))(implicit contravariant: Contravariant[F]): F[Z] = contravariant.contramap(t1._1)(f)
  def imap[Z](f: (A0) => Z)(g: Z => (A0))(implicit invariant: Invariant[F]): F[Z] = invariant.imap(t1._1)(f)(g)
  def apWith[Z](f: F[(A0) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap(f)(t1._1)
}
private[syntax] final class Tuple2CartesianOps[F[_], A0, A1](t2: Tuple2[F[A0], F[A1]]) {
  def map2[Z](f: (A0, A1) => Z)(implicit functor: Functor[F], cartesian: Cartesian[F]): F[Z] = Cartesian.map2(t2._1, t2._2)(f)
  def contramap2[Z](f: Z => (A0, A1))(implicit contravariant: Contravariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.contramap2(t2._1, t2._2)(f)
  def imap2[Z](f: (A0, A1) => Z)(g: Z => (A0, A1))(implicit invariant: Invariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.imap2(t2._1, t2._2)(f)(g)
  def apWith[Z](f: F[(A0, A1) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap2(f)(t2._1, t2._2)
}
// etc.

It seems like there are conflicting concerns here about name clashes vs. consistency.
@yilinwei do you think @ceedubs' point about the difficulty with tuples and SI-2712 suggests that we should leave both Cartesian and tuple syntax in place for now? Just Cartesian?

@yilinwei
Copy link
Contributor

@DavidGregory084, @ceedubs I think that, that would depend on #1073.Do you mind checking that Validated/Either work with the SI-2712 plugin?

My own thoughts on #1073 is that we should assume that people include in in there project as I'm not aware of any downsides to this. If it works with SI-2712 and it's decided that we assume that pre 2.12 projects use the plugin then I would be in favour of removing the |@| syntax and changing the associated material in the docs. Ideally this would be done pre 1.0.0.

I don't think anyone is in love with extra syntax though I could be wrong 😄.

@DavidGregory084
Copy link
Member Author

@yilinwei yeah, that's a good idea, I'll see if I can do that within the next couple of days

@non
Copy link
Contributor

non commented Jun 15, 2016

@ceedubs Wouldn't Tuple2 have a Bifunctor instance instead?

I like the approach taken in this PR. And I agree that if it works reliably I'd like to deprecate the |@| syntax (maybe moved into cats.legacy or something) and update the documentation. I don't think we have to do that in this PR though.

👍 once @DavidGregory084 is happy with the PR.

@ceedubs
Copy link
Contributor

ceedubs commented Jun 15, 2016

Wouldn't Tuple2 have a Bifunctor instance instead?

@non in scalaz it has both, which I find a bit convenient but it's fine if we don't do that in cats. The Functor instance is right-biased just as it would be for Xor or, more similarly, Writer.

@kailuowang
Copy link
Contributor

I like this approach. I have some thoughts around the name map as well.
Probably due to my limited experience to FP, I kind of register the word map with an operation that maintains the outer data shape (e.g. Functor.map) . The CartesianBuilder's map changes the outer data shape (in some sense it's like an instance of FunctionK), but the fact that it's a Builder helped me avoid confusion. Now that a Tuple has a map2 and a bimap with rather different semantic meaning feels a bit awkward to me.
I am wondering what's the original rationale behind naming the build method in CartesianBuilder as map v.s. say build or compose.

@non
Copy link
Contributor

non commented Jun 15, 2016

We could also call the implicit methods here cmap (for cartesian-map) if that seems better. I'm not sure -- to me, map2 is different enough from other things that the reader can hopefully distinguish them.

@ceedubs
Copy link
Contributor

ceedubs commented Jun 15, 2016

I'm fine with either map2, map3, etc or something like cmap (or mapN or something). But I do think that maybe we shouldn't call this map.

@non
Copy link
Contributor

non commented Jun 15, 2016

Yeah, I agree, I think we should distinguish from map here.

@kailuowang
Copy link
Contributor

I am fine with either map2, map3 etc or something like cmap as well, depends on if we prefer an arity generic method name.

@adelbertc
Copy link
Contributor

I'd vote cmap if only to distinguish it from the usual mapN names

@DavidGregory084
Copy link
Member Author

DavidGregory084 commented Jun 15, 2016

Thanks for all the review, guys 👍

@yilinwei @non as expected this syntax works fine for e.g. Either with the SI-2712 plugin, but without it requires a type alias - it seems that deprecating |@| accordingly depends upon what happens in #1073.

With respect to the naming, cmap is great, but what about the other ops? ccontramap looks like a spelling mistake 😛

@ceedubs
Copy link
Contributor

ceedubs commented Jun 16, 2016

ccontramap looks like a spelling mistake

Hmm that's a pretty good point @DavidGregory084 :). Personally I kind of like mapN and contramapN (where N is literal as opposed to being a number that increases for each arity). @adelbertc in my mind this is pretty much like map, but it allows you to map over N values at a time, so it kind of works. But I don't feel strongly.

@DavidGregory084
Copy link
Member Author

DavidGregory084 commented Jun 16, 2016

@ceedubs I quite like mapN / contramapN - unless anyone has any objections I think I will go ahead with that?

I have to admit that personally I am kind of attracted to map2 / map3 etc. if only because new users may find those names to be familiar from the red book - in effect this syntax uses a tuple as a parameter list to those ops

@kailuowang kailuowang mentioned this pull request Jun 17, 2016
@ceedubs
Copy link
Contributor

ceedubs commented Jun 18, 2016

@DavidGregory084 I'm perfectly happy with either mapN or map2, map3, etc. Happy to go along with whichever other people prefer.

@adelbertc
Copy link
Contributor

I'm OK with mapN (where N is N and not the arity of the tuple)

@adelbertc
Copy link
Contributor

adelbertc commented Aug 4, 2016

@DavidGregory084 Are you still able to work on this?

On a side note, due to the issues in choose mapN vs map2/3/4.. vs. cmap, I now find myself having a strong preference of making it just apply, so that the syntax looks like (fa, fb, fc) { case (a, b, c) => f(a, b, c) }

@DavidGregory084
Copy link
Member Author

@adelbertc yes, very sorry for sitting on this one, I've been dealing with some family illness so this fell by the wayside; should be able to take a look this evening.

We would still need contramapN / imapN etc. though, right?

@adelbertc
Copy link
Contributor

@DavidGregory084 I'm sorry to hear that, I hope everything is OK. No worries at all, family comes first. If you'd like, just say the word and I or another contributor can handle the rest of this patchset.

Good point on contramap and imap. I gave another look through the comments on this PR and I'm back to thinking map{2, 3, ...} as it mirrors the method names on Apply. Unless anyone has objects I propose map{2, 3..}, contramap{2, 3..}, and imap{2, 3..}.

@DavidGregory084
Copy link
Member Author

DavidGregory084 commented Aug 8, 2016

@adelbertc Yes, a bit of normality has been restored now, hence why I've been looking at my GH notifications. You guys have been busy! 😄 0.7.0 looks awesome.

As you are the only person who has expressed a strong preference about the naming so far (and I have a slight preference for this too) I have updated this PR and stuck with map{2, 3..} for now, I hope that is okay with everyone!

@@ -40,6 +40,7 @@ trait AllSyntax
with TransLiftSyntax
with TraverseFilterSyntax
with TraverseSyntax
with TupleSyntax
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to also have object tuple extends TupleSyntax in package.scala right?

Also, do we want to put this under tuples or Cartesian syntax?

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 yes, well spotted - thanks! I'll update this.

I am not really sure about the second question; my reasoning was that in general the syntax is named according to the type or set of types that it decorates but I may have got the wrong idea and I'm happy to rename it.

@adelbertc
Copy link
Contributor

LGTM 👍 I'm not sure what others thoughts are, but I feel like this should be under apply or cartesian syntax. Will wait for others to chime in.

@kailuowang
Copy link
Contributor

👍

@non
Copy link
Contributor

non commented Aug 12, 2016

Seems OK to me. 👍

I agree with @adelbertc that we may want to move it but I'm OK merging this then making a decision.

@non non merged commit a0aa6e0 into typelevel:master Aug 12, 2016
@DavidGregory084 DavidGregory084 deleted the tuple-syntax branch August 12, 2016 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants