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

Optimize Apply methods in FlatMap #2597

Merged
merged 4 commits into from
Nov 9, 2018
Merged

Conversation

kailuowang
Copy link
Contributor

I noticed that these implementations can be slightly improved. Given their common usages I think it would worth it.
related issue typelevel/cats-effect#401

Copy link
Member

@LukaJCB LukaJCB left a comment

Choose a reason for hiding this comment

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

👍

@codecov-io
Copy link

codecov-io commented Nov 5, 2018

Codecov Report

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

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2597      +/-   ##
==========================================
+ Coverage   95.16%   95.16%   +<.01%     
==========================================
  Files         361      361              
  Lines        6634     6638       +4     
  Branches      294      295       +1     
==========================================
+ Hits         6313     6317       +4     
  Misses        321      321
Impacted Files Coverage Δ
core/src/main/scala/cats/FlatMap.scala 86.66% <100%> (+4.84%) ⬆️

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 1aac375...0a9f9d9. Read the comment docs.

@ceedubs
Copy link
Contributor

ceedubs commented Nov 6, 2018

I think that this is probably a good change to make, but I'm wondering if it should go into a new minor version as opposed to a patch. My concern is that someone might have an IO/Future-like type where ap runs effects in parallel but a FlatMap instance is also defined. While we have laws that say that ap and flatMap should be consistent, our equality is only based on result values and not rumtime execution characteristics, so it's debatable whether or not their instance broke laws. So I wouldn't want to tank someone's performance without sufficient warning.

In fact I wonder whether this might apply to our instances for standard library Future?

@kailuowang
Copy link
Contributor Author

kailuowang commented Nov 6, 2018

@ceedubs this is probably not a breaking change because currently FlatMap already overrides product with implementation using flatMap. And product is called by these methods. So if a FlatMap instance is used, it's already using flatMap v.s. ap, e.g. the current implementation of productL and productR in FlatMap is essentially:

 map(flatMap(fa)(a => map(fb)(b => (a, b))))((a, _) => a)
 map(flatMap(fa)(a => map(fb)(b => (a, b))))((_, b) => b)

You can see wasteful maps.

So I don't blieve this change will change the effect.
I am not 100% sure if product being overriden in FlatMap is more correct at the first place, but my thinking is that user should avoid getting tied to the concrete effect type F[_], and if they keep effect type abstract and use a context bound F[_]: FlatMap then they should expect sequential execution anyway.

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.

Ah, okay thanks for the explanation @kailuowang. This sounds good to me (but maybe we should let this hang around just a bit longer in case anyone else has concerns).

flatMap(fa)(a => map(fb)(b => f(a, b)))

override def productR[A, B](fa: F[A])(fb: F[B]): F[B] =
flatMap(fa)(_ => fb)
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming back to this, the lack of symmetry between productR and productL feels a little off to me. I think that the override of map2 makes sense. But maybe productL and productR should both just delegate to map2?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unlike productL, productR using map2 will incur one extra map. That's where the asymmetry comes from.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh right. Sorry that I've required so much explanation on this one 😬

LGTM. @mpilquist does this PR look good to you?

Copy link
Member

Choose a reason for hiding this comment

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

Yep looks good to me

@kailuowang kailuowang added this to the 1.5 milestone Nov 8, 2018
@kailuowang kailuowang merged commit 97dc0c2 into typelevel:master Nov 9, 2018
@kailuowang kailuowang deleted the productRL branch November 9, 2018 14:08
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