-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Stack-safe FreeAp #1748
Conversation
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
5e6adae
to
10c6516
Compare
10c6516
to
94159b9
Compare
### 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
74554b5
to
bcf954d
Compare
*/ | ||
final def fold(implicit F: Applicative[F]): F[A] = | ||
* Tail recursive only if `F` provides tail recursive interpretation. | ||
*/ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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]) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 😛
There was a problem hiding this comment.
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") { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it.
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. |
1afce03
to
c428c45
Compare
c428c45
to
92b6edc
Compare
@kailuowang I did some basic benchmarks. A right-associated call tree with 50000 I also converted the tests I thought were appropriate to scalacheck. |
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] = |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Thanks. The only minor comment left from my side is the |
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this 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.
@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. |
This indeed feels a bit magical. I played around a bit to get a feel what is going on and ended up refactoring See edmundnoble/cats@stacksafefreeap...peterneyens:stacksafefreeap-peter |
@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 |
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 |
merging with 2 sign-offs |
Port from JS, from @safareli's implementation. It's stack-safe, I just need to benchmark and comment it.
Fixes #1151.