-
-
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
Kliesli is not stack-safe on left-associated binds #1733
Comments
In years of scalaz usage, the stack unsafety of I think that safety (no runtime surprises) is a major argument for reformulating I'm assigning this to @adelbertc and @edmundnoble, who have done a lot of work with monad transformers. I'm also curious whether @johnynek has any thoughts on this. |
I don't believe this is correct, because your constituent monad is
To my mind the issue is important enough to merit 3 eventually, but 2 seems like an alright first approximation. I think the |
I think if we are going to hand inline we need to have benchmarks to show that it is actually faster, personally. I think if we can implement a tailRecM, which we can for Kleisli, then we should consider recommending (and possibly making some functions to make it easier), when you need a recursive bind on Kleisli (or other monads) to jump into Something like this: def toFree[A, B, M[_]](k: Kleisli[M, A, B]): Free[Kleisli[M, A, ?], B] =
Free.lift(k)
def toKleisli[A, B, M[_]](f: Free[Kleisli[M, A, ?], B])(implicit M: Monad[M]): Kleisli[M, A, B] =
f.runTailRec which works for any monad (just call lift and runTailRec). You could even imagine a macro that could possibly replace an expression on one Monad with this lifted-and-runTailRec version. |
I actually have run into stack-safety issues with Kleisli, at least the Scalaz version, when I was using something like Kleisli with Task to do retries of an IO action. I don't remember what specifically what I was doing though. |
I'd be happy to implement this if someone could provide guidance. A |
@johnynek If I understand correctly, we could support stack safe left binds for Kleisli without a binary breaking change, if that's the case, this issue probably doesn't need to block RC1 right? |
@kailuowang I don't know a way to make it stack safe. What I propose is that when people need this they lift into |
@johnynek sorry I missed your reply. Yeah, that's how I understand it as well. it's sort of a work around, not a change to the Kleisli. Is this idea similar to what is suggested in https://twitter.com/puffnfresh/status/939083836628930560 ? |
Hi all, Trying to implement def flatMap[A, B](fa: Kleisli[F, R, A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, B] =
Kleisli { r =>
F.suspend(fa.run(r).flatMap(f.andThen(_.run(r))))
} This fixes the issue because it introduces a "lazy boundary" before "fa.run(r)", so it suspends evaluation before "fa.run" happens, so a new Now @edmundnoble is telling me that doing this in cats-effect is problematic due to coherence rules, so our
def flatMap[A, B](fa: Kleisli[F, R, A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, B] =
Kleisli[F, R, B] { r =>
F.now(r).flatMap(r => fa.run(r).flatMap(f.andThen(_.run(r))))
} Note:
Also, we need a solution for the upcoming cats-effect and we need it now, so I'd appreciate your thoughts on points 1, 2 or 3. Btw, above you spoke about the trick of Well, I hate to break it to you, but This too can be fixed, but I'm not sure you'll like it. |
Update, I now have a PR in Cats for your consideration: #2185 |
Related, the issue and fix for |
This law fails. The reason this law fails is here:
A chain of right-associated binds will push inside the lambda argument to
flatMap
, meaning it will be subject to the stack safety of the constituent monadF[_]
. A chain of left-associated binds will result in a proportionally long chain ofrun(r)
. In order to solve this, we would need to use a similar technique as to what is done inStateT
.The text was updated successfully, but these errors were encountered: