-
-
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
Fix #1733: make Kleisli.flatMap stack safe #2185
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2185 +/- ##
==========================================
+ Coverage 94.75% 94.75% +<.01%
==========================================
Files 330 330
Lines 5568 5570 +2
Branches 201 204 +3
==========================================
+ Hits 5276 5278 +2
Misses 292 292
Continue to review full report at Codecov.
|
A couple practical observations:
I don't have any benchmarks from real projects, but my gut feeling is that this is a good trade. |
@rossabaker thanks. As said in the issue that this solves, the stack unsafety is for "left-associated binds", which for our purposes are less important than "right-associated binds", which are those that happen in recursive loops. I suspect the taxes are going up due to an extra I would be interested about Kleisli usage in the wild and what data types people use for |
In descending order of |
These kinds of type system hacks always make me a bit weary (though tbh, I played around with implementing stack-safe function composition by using a big fat |
Close #1733.
This is badly needed in
cats-effect
in order to define aSync[Kleisli[F, R, ?]]
implementation.I'm already pushing one right now, but with a patched
flatMap
implementation that would no longer preserve coherence with the officialflatMap
of Kleisli. So it would be nice if both these PRs happen, see: typelevel/cats-effect#144The way to fix it is to define a
flatMap
implementation that triggers theF[_]
context earlier. Like this:Unfortunately this solution doesn't work directly because we have a
FlatMap
restriction on that operation, notMonad
. And so the trick is to discriminate between plainFlatMap
andMonad
via inheritance and the subtyping encoding that we're all doing, this being a subtitute forKleisli.apply
:And now the
flatMap
definition can be:As proven by the tests with
Eval
, this works. And it will work splendidly withcats-effect
.FAQ
In preparation for the upcoming discussions, I can think of the following ...
Is there any other solution possible?
No, this is the only fix possible for Cats version 1.x, given the
A => F[B]
encoding.We could end up with a different internal encoding, say
F[A => F[B]]
, but note that such an encoding doesn't guarantee a fix either. In spite of popular belief note thatStateT
isn't currently stack safe for left binds either, see: typelevel/cats-effect#139Doesn't this assume that a stack safe
F.pure(a).flatMap(f)
is lazy?Yes, however it is my firm belief that no stack safe
flatMap
can be defined with strict evaluation, not even when the source isF.pure
. This is because you can always define a recursive structure that's already evaluated (in memory) and that triggers stack overflows.In other words if
F[_]
really has a stack safeflatMap
(that could pass the laws ofSync
incats-effect
for example, or that could describetailRecM
), then the behavior ofF.pure(a).flatMap(f)
is necessarily lazy.For details and a working example: typelevel/cats-effect#92
Isn't usage of isInstanceOf a hack?
A much more elaborate signature would do something like the Priority pattern in Algebra.
However consider that:
CanBuildFrom
signaturesMonad
definition comes from a different place thanFlatMap
) are totally uninteresting; the Scala compiler will pickMonad
if in scope because it has a preference for sub-types or in case they happen to have the same "score", it yields a conflict anywayMost importantly is that this PR solves a problem and without it we cannot solve that problem.
Perfect is the enemy of good 😉