-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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 😉