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

Stack-safe FreeAp #1748

Merged
merged 6 commits into from
Jul 27, 2017
Merged

Stack-safe FreeAp #1748

merged 6 commits into from
Jul 27, 2017

Conversation

edmundnoble
Copy link
Contributor

@edmundnoble edmundnoble commented Jun 28, 2017

Port from JS, from @safareli's implementation. It's stack-safe, I just need to benchmark and comment it.

Fixes #1151.

@codecov-io
Copy link

codecov-io commented Jun 28, 2017

Codecov Report

Merging #1748 into master will increase coverage by 0.05%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1748      +/-   ##
==========================================
+ Coverage   94.81%   94.87%   +0.05%     
==========================================
  Files         241      241              
  Lines        4092     4139      +47     
  Branches       95      103       +8     
==========================================
+ Hits         3880     3927      +47     
  Misses        212      212
Impacted Files Coverage Δ
...rc/main/scala/cats/laws/discipline/Arbitrary.scala 92.18% <ø> (ø) ⬆️
...ree/src/main/scala/cats/free/FreeApplicative.scala 100% <100%> (ø) ⬆️

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 6fefa30...1d32d09. Read the comment docs.

@edmundnoble edmundnoble force-pushed the stacksafefreeap branch 4 times, most recently from 5e6adae to 10c6516 Compare July 5, 2017 01:06
@edmundnoble edmundnoble changed the title Stack-safe FreeAp (WIP, #1151) Stack-safe FreeAp (#1151) Jul 5, 2017
@edmundnoble edmundnoble changed the title Stack-safe FreeAp (#1151) Stack-safe FreeAp Jul 5, 2017
### The way FreeApplicative#foldMap works
Despite being an imperative loop, there is a functional intuition behind `FreeApplicative#foldMap`.

The new `FreeAp`'s `foldMap` is a sort of mutually-recursive function that operates on an argument stack and a
Copy link
Contributor

Choose a reason for hiding this comment

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

quick nitpick question: shall we use FreeAp or FreeApplicative in the documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I'll try to use FreeApplicative. My bad.

Copy link
Contributor

Choose a reason for hiding this comment

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

looks like you forgot about this one?

*/
final def fold(implicit F: Applicative[F]): F[A] =
* Tail recursive only if `F` provides tail recursive interpretation.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

another nitpick here, the scala doc indentation is changed away from our standard.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think I know what tail recursive implementation means for Applicative. Can we be more concrete or link somewhere in this comment?

Copy link
Contributor Author

@edmundnoble edmundnoble Jul 19, 2017

Choose a reason for hiding this comment

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

It's stack-safe. Edit: I see what you mean now. I'll remove this comment, it doesn't make sense to me :P

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

foldMap(FunctionK.id[F])
}
Copy link
Contributor

Choose a reason for hiding this comment

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

yet another superficial comment, what about all these addition of {. If we want to change the formating rule, shall we use another separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IntelliJ is conspiring against me 😛

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -56,6 +64,17 @@ class FreeApplicativeTests extends CatsSuite {
r1.foldMap(nt) should === (r2.foldMap(nt))
}

test("FreeApplicative#flatCompile") {
Copy link
Contributor

@kailuowang kailuowang Jul 19, 2017

Choose a reason for hiding this comment

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

How about making this test a scalacheck? In fact, to gain more confidence against the new implementation, how about we convert the existing related tests to scalacheck tests?

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'll do that, and add an Arbitrary which is more exhaustive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doing this has exposed a ClassCastException bug in the implementation, which I'm working through now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

@kailuowang
Copy link
Contributor

I left a couple of superficial comments. To me, it's a bit too magical to validate eyeball, I think making all relevant tests more robust (by converting them to scalacheck) would help. I am also curious about the benchmarks.

@edmundnoble edmundnoble force-pushed the stacksafefreeap branch 2 times, most recently from 1afce03 to c428c45 Compare July 20, 2017 05:38
@edmundnoble
Copy link
Contributor Author

edmundnoble commented Jul 21, 2017

@kailuowang I did some basic benchmarks. A right-associated call tree with 50000 Ap calls takes as long to construct and evaluate now as one with 1000 leaves did previously. We're at 50x faster, presumably mostly because Ap used to take O(n) time and now takes O(1). I tried evaluating the same 50000 Ap tree using the old implementation, and my system ran out of RAM 😛

I also converted the tests I thought were appropriate to scalacheck.

@safareli
Copy link

@kailuowang I did some basic benchmarks. A right-associated call tree with 50000 Ap calls takes as long to construct and evaluate now as one with 1000 leaves did previously. We're at 50x faster, presumably mostly because Ap used to take O(n) time and now takes O(1). I tried evaluating the same 50000 Ap tree using the old implementation, and my system ran out of RAM 😛

Perfect 🎉 💃

@@ -165,6 +166,22 @@ object arbitrary extends ArbitraryInstances0 {

implicit def catsLawsArbitraryForReaderWriterStateT[F[_]: Applicative, E, L, S, A](implicit F: Arbitrary[(E, S) => F[(L, S, A)]]): Arbitrary[ReaderWriterStateT[F, E, L, S, A]] =
Arbitrary(F.arbitrary.map(ReaderWriterStateT(_)))

implicit def catsLawsArbitraryForListNatTrans: Arbitrary[List ~> List] =
Copy link
Contributor

Choose a reason for hiding this comment

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

These two arbitrary instances seems a bit too, well, arbitrary and limited to be included in discipline for general uses. (actually the second one might be unused according to the codcov), how about we move them into the tests?

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'll put the used one into FreeApplicativeTests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@kailuowang
Copy link
Contributor

Thanks. The only minor comment left from my side is the FreeAp wording in the documentation.

@edmundnoble
Copy link
Contributor Author

Oh wow, thought I fixed that a while ago. Done.

}

implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], FF: Arbitrary[(A, A) => A], A: Arbitrary[A]): Arbitrary[FreeApplicative[F, A]] =
Arbitrary(freeGen[F, A](2))
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I meant to ask this last time but somehow lost it. Did you try maxDepth larger than 2? The main reason I ask here is that it seemed to me that you were preparing for testing maxDepth more than 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I was, I forgot to raise it again after lowering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

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.

👍 It's a bit risky but the gain is substantial.

@kailuowang
Copy link
Contributor

@peterneyens @johnynek since you two are the only other maintains left around, got some time to review this one? It would nice to be included in the MF release.

@peterneyens
Copy link
Collaborator

peterneyens commented Jul 26, 2017

This indeed feels a bit magical.

I played around a bit to get a feel what is going on and ended up refactoring foldMap a bit. I haven't done any benchmarks to compare it, but it seems to pass the tests.
It is probably a bit slower, but I think it might be easier to comprehend than the more imperative implementation.

See edmundnoble/cats@stacksafefreeap...peterneyens:stacksafefreeap-peter

@edmundnoble
Copy link
Contributor Author

@peterneyens Wow, thank you, this is really readable. It looks like it has to be noticeably less performant than the imperative implementation (extra intermediate list which is appended to the right and extra tuple allocation introduced while reassociating an Ap chain) but I think I can use this to improve the comments a lot.

@peterneyens
Copy link
Collaborator

Thanks for the improved documentation @edmundnoble.

Given the considerable performance improvements and the added stack safety, lets merge this as is, so we don't have to hold the 1.0.0-MF release any longer 👍 (Looks like we need to fix some conflicts first though).

We can look if we can clean up the foldMap implementation later on.

@kailuowang
Copy link
Contributor

merging with 2 sign-offs

@kailuowang kailuowang merged commit 693f456 into typelevel:master Jul 27, 2017
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