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

Added merge (product) to Arrow for arrows composition #2063

Merged
merged 19 commits into from
Dec 8, 2017

Conversation

marcobattaglia
Copy link
Contributor

@marcobattaglia marcobattaglia commented Dec 5, 2017

merge &&&

scala> import cats.implicits._
scala> import cats.arrow.Arrow
scala> val addEmpty: Int => Int = _ + 0
scala> val multiplyEmpty: Int => Double= _ * 1d
scala> val f: Int => (Int, Double) = addEmpty &&& multiplyEmpty
scala> f(1)
res0: (Int Double) = (1,1.0)

combineAndByPass -<

Create a new computation F that apply f andThen biFork the result
one way is applied to g and the other pass through
the result is a tuple

                 /-----out1 = f(input) -----\
      input ----{                            }----(out1,out2)
                 \-----out2 = g(f(input))---/

Example:

     scala> import cats.implicits._
     scala> import cats.arrow.Arrow
     scala> val twoTimes: Int => Double = _ * 2d
     scala> val fiveTimes: Double => Double= _ * 5
     scala> val f: Int => (Double, Double) = twoTimes -< fiveTimes
     scala> f(2)
     res0: (Double Double) = (4.0,20.0)

@kailuowang
Copy link
Contributor

will need to add some mima exceptions

exclude[ReversedMissingMethodProblem]("cats.arrow.Arrow.merge"),
exclude[ReversedMissingMethodProblem]("cats.arrow.Arrow.combineAndByPass"),
exclude[ReversedMissingMethodProblem]("cats.arrow.Arrow#Ops.merge"),
exclude[ReversedMissingMethodProblem]("cats.arrow.Arrow#Ops.combineAndByPass"),
exclude[ReversedMissingMethodProblem]("cats.arrow.Arrow#Ops.&&&"),
exclude[ReversedMissingMethodProblem]("cats.arrow.Arrow#Ops.-<"),

to here

@kailuowang
Copy link
Contributor

I took the liberty to update your comment with some formatting. Hope you don't mind.

@marcobattaglia
Copy link
Contributor Author

HI Kailuowang. I have committed the exclude directives. Do I need to do another pull request or is it ok for you?
Thanks for your better formatting.

@kailuowang
Copy link
Contributor

You are welcome! Everything related in one PR would be better.

@kailuowang kailuowang added this to the 1.0.0 milestone Dec 5, 2017
@kailuowang
Copy link
Contributor

kailuowang commented Dec 5, 2017

some errors in the doctest

m/home/travis/build/typelevel/cats/core/.jvm/target/scala-2.11/src_managed/test/cats/arrow/ArrowDoctest.scala:46: identifier expected but ')' found.
/home/travis/build/typelevel/cats/core/.jvm/target/scala-2.11/src_managed/test/cats/arrow/ArrowDoctest.scala:50: ')' expected but '}' found.
/home/travis/build/typelevel/cats/core/.jvm/target/scala-2.11/src_managed/test/cats/arrow/ArrowDoctest.scala:65: identifier expected but ')' found.�
/home/travis/build/typelevel/cats/core/.jvm/target/scala-2.11/src_managed/test/cats/arrow/ArrowDoctest.scala:69: ')' expected but '}' found.�

To run doctest, sbt coreJVM/test

@codecov-io
Copy link

codecov-io commented Dec 5, 2017

Codecov Report

Merging #2063 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2063      +/-   ##
==========================================
+ Coverage   94.66%   94.67%   +<.01%     
==========================================
  Files         318      318              
  Lines        5380     5386       +6     
  Branches      112      216     +104     
==========================================
+ Hits         5093     5099       +6     
  Misses        287      287
Impacted Files Coverage Δ
...a/cats/laws/discipline/CommutativeArrowTests.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/arrow/Arrow.scala 100% <100%> (ø) ⬆️
...c/main/scala/cats/laws/discipline/ArrowTests.scala 100% <100%> (ø) ⬆️
laws/src/main/scala/cats/laws/ArrowLaws.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/data/IorT.scala 97.39% <0%> (-0.03%) ⬇️
core/src/main/scala/cats/MonoidK.scala 80% <0%> (ø) ⬆️
core/src/main/scala/cats/Alternative.scala 88.88% <0%> (ø) ⬆️
core/src/main/scala/cats/SemigroupK.scala 100% <0%> (ø) ⬆️
...n/scala/cats/laws/discipline/MonadErrorTests.scala 100% <0%> (ø) ⬆️
core/src/main/scala/cats/MonadError.scala 100% <0%> (ø) ⬆️
... and 8 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 c51fe61...4caa33e. Read the comment docs.


/**
* Create a new computation `F` that apply f andThen biFork the result
* one way is applied to g and the other pass through
Copy link
Contributor

@kailuowang kailuowang Dec 6, 2017

Choose a reason for hiding this comment

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

This description kind of implies that f is only applied once, but actually it is applied twice right? the diagram is more accurate.

@kailuowang
Copy link
Contributor

kailuowang commented Dec 6, 2017

This merge is basically Semigroupal.product, if you just care about Function or Kleisli they already have Semigroupal instance
What this proves, though, is that Arrow forms a Semigroupal
We could add the following to Semigroupal's companion

  implicit def catsSemigroupalForArrow[F[_, _], A](implicit F: Arrow[F]): Semigroupal[F[A, ?]] = new Semigroupal[F[A, ?]] {
    def product[B, C](fb: F[A, B], fc: F[A, C]): F[A, (B, C)] =
      F.andThen(F.lift((x: A) => (x, x)), F.split(fb, fc))
  }

let me punt on if we should add this though.

@kailuowang
Copy link
Contributor

I think this PR is good as is. I thought about renaming merge to product but I think it might cause syntax conflicts between ArrowSyntax and SemigroupalSyntax.
We can add the Semigroupal instance for F[_, ?] with Arrow in a followup PR after this one is merged.

kailuowang
kailuowang previously approved these changes Dec 6, 2017
…n in doc was not visibile in browser. Deleted the last two rows of merge method scala doc: the schema is clear enough.
kailuowang
kailuowang previously approved these changes Dec 6, 2017
LukaJCB
LukaJCB previously approved these changes Dec 6, 2017
*
*/
@simulacrum.op("-<", alias = true)
def combineAndByPass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you explain the meaning behind this name? Should it be Bi instead of By? It makes me think of the word bypass, but I suspect you weren't going for that based on the camelCasing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry it should be 'bypass' without camelCase. I'm converting the camel to a dromedary ;-) and the I'll commit.

@ceedubs
Copy link
Contributor

ceedubs commented Dec 6, 2017

Thanks @marcobattaglia!

I definitely like having the merge, even if we decide to separately define a Semigroupal for all arrows. The use-cases for the -< method seem a little more narrow to me, but my only concerns about it are the textual name (see comment above), and potential bloating of type class methods. If people find it to be useful, I have no objections.

@marcobattaglia
Copy link
Contributor Author

@ceedubs
We have often used these methods in our projects, and it happens to compose functions using -< .
We have also same methods to compose 'Kleisli arrows' but I need to study better cats in order to decide if it makes sense to prepare a PR for those . Thank you for your support and consideration.

kailuowang
kailuowang previously approved these changes Dec 6, 2017
@kailuowang kailuowang dismissed their stale review December 6, 2017 19:44

having 2nd thought.

@kailuowang
Copy link
Contributor

Actually, before I sign off this PR, I start to wonder what would be the difference between
calling combineAndBypass(f, g) and calling merge(f, f >>> g)?

*/
@simulacrum.op("-<", alias = true)
def combineAndBypass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)] = {
andThen(lift((x: A) => (x, x)), split(f, andThen(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.

One downside of this is that it calculates f(x) twice. I think that for pretty much any arrow you would want to override this to reuse the result of f(x).

Copy link
Contributor

@kailuowang kailuowang Dec 6, 2017

Choose a reason for hiding this comment

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

I agree. I think I incline to not adding this one, and the default impl is just an alias to merge(f, f >>> g)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes you are right. It is also the same of this one:
andThen(andThen( f, lift((x: B) => (x, x)) ), split(id, g))
This last one evicts the double calculation.
Essentially: input=> f(input) => out1=> (out1,out1)=> (out1,g(out1))=>(out1,out2)
Please consider if it's better to eliminate combineAndBypass because it is possible to use merge(f, f >>> g) or if it is good to have combineAndBypass as a sort of shortcut.
I'll prepare the commit following your decision.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mmm.... I think that also merge(f, f >>> g) evaluates f twice, instead this one andThen(andThen( f, lift((x: B) => (x, x)) ), split(id, g)) evaluates f one time, so that it is not properly a simple shortcut althought the result is the same.

*
*/
@simulacrum.op("-<", alias = true)
def combineAndBypass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be andThen(f, merge (id, g)) right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right. Do you prefer to eliminate the combineAndBypass or to leave it as a simple shortcut? For me it's the same, I think that it depends on how cats is generally organized. I will commit following your opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've tries to rewrite andThe(f, merge(d,g)) as something like andThen(f, catsSemigroupalForArrow.product(id,g)) and I've noticed that catscatsSemigroupalForArrow.product is a little bit different than merge infact the product change also the types of the resulting function.
For example the type of
catsSemigroupalForArrow.product( ((x:Int) => x * 2d), ((x:Double) => x * 5) ) is Double with Int => (Double, Double) , instead the type of `((x:Int) => x * 2d) merge ((x:Int) => x * 5)' is Int => (Double, Int). My question is: 'product' means also an operation on types, in the sense that it also work on the domain types of the function ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Your catsSemigroupalForArrow.product( ((x:Int) => x * 2d), ((x:Double) => x * 5) ) is supprising. I didn't expect that to compile. We can discuss that #2078

If you don't mind I'd prefer removing combineAndBypass. It's not providing much, typing f >>> (id &&& g) isn't too much worse than f -< g which arguably is more obscure to read. And it's a lot harder to remove a method than adding one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I will prepare the commit to delete it. Thx

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this capanion object: object Arrow { implicit def catsSemigroupalForArrow[F[_, _], A](implicit F: Arrow[F]): Semigroupal[F[A, ?]] = new Apply[F[A, ?]] { def map[B, C](fb: F[A, B])(f: B => C): F[A, C] = F.rmap(fb)(f) def ap[B, C](ff: F[A, B => C])(fb: F[A, B]): F[A, C] = F.rmap(F.andThen(F.lift((x: A) => (x, x)), F.split(ff, fb)))(tup => tup._1(tup._2)) } compiles.

*
* In the reference articles "Arrows are Promiscuous...", and in the corresponding Haskell
* library `Control.Arrow`, this function is called `arr`.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

I hate to nitpick on this, but this format is unnecessarily changed away from the scala doc convention.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Excuse me kailuowang, but I don't understand. What format is changed away? I don't know where I must correct the format, because I don't notice any difference.Please show me better.thx

Copy link
Contributor

Choose a reason for hiding this comment

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

The indentations of the * :)

Copy link
Contributor

Choose a reason for hiding this comment

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

again, it's a total nitpick. I think it's ready to merge.

@kailuowang kailuowang changed the title Added two methods to Arrow for arrows composition Added merge (product) to Arrow for arrows composition Dec 8, 2017
kailuowang
kailuowang previously approved these changes Dec 8, 2017
@marcobattaglia
Copy link
Contributor Author

Ops! Space! Sorry, I've committed just now.

Copy link
Contributor

@kailuowang kailuowang left a comment

Choose a reason for hiding this comment

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

Thanks so much!

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

5 participants