-
-
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
Proposal: move tailRecM to FlatMap and remove FlatMapRec #1278
Comments
So after some thought and hearing about @tpolecat and @raulraja 's experience of migrating to Cats 0.7.x, I'm becoming open to the idea of just having My concern is primarily that of principle - Tagging people who expressed an opinion on this before: @tpolecat @non @tixxit @travisbrown @mpilquist @ceedubs @zainab-ali @johnynek I would also appreciate any feedback from @paf31 :-) |
Is there a safe implementation for the list monad? |
There is a safe implementation for everything in cats including List and
|
@paf31 We can make it safe, heap permitting, since Scala allows mutability:
There was also a related question to that here: #1329 |
I'm assuming this is the issue @johnynek was referencing… This is a lawful monad which does not have a stack-safe A more practical example is These use-cases show up in general if you try to define a function |
Copying over some context from Gitter… @johnynek countered with the // given a conventional Cont implementation and:
type JCont[R, A] = Free[Cont[R, ?], A]
val evil: Cont[R, JCont[R, A]] = ???
val lifted: JCont[R, JCont[R, A]] = JCont.liftM(evil)
val oops = lifted.join Even assuming a Another way of looking at this is in the context of object Task {
def now(a: A): Task[A] = ???
def async(a: (A => Unit) => Unit)(implicit S: Strategy): Task[A] = ???
def asyncUnshift(a: (A => Unit) => Unit): Task[A] = ???
} The first and second functions here will be stack-safe, and if Note that the |
It's true that without stack safe functions, which we could add, I see no way to make Here is a link to recent discussion: https://gitter.im/typelevel/cats?at=58d83c68b402a53211b5931a Since this is the only real case we know of, I think we should attack this more directly than giving up tailRecM on all cats.Monads, since so many implementations are non-obvious, yet without them people have empirically just written unsafe code using recursive flatMap. In my view, that is much worse, since unlike |
As I mentioned on Gitter, what this comes down to for me is whether or not we're ok with If we take the strict view on I want to be very clear that this isn't a matter of insufficient creativity in So I think the only general solution to this is to just accept that |
See this PR: #1216
Note the discussion about adding two different implementations of many of the methods, one using tailRecM the other not.
It seems that all the monads in cats can support tailRecM. In fact, it seems that if look at two classes of Monads we have "strict" (Option, Xor, Either, List, Vector, ...) and "lazy" (Free, Eval, State, ....)
In the case of the lazy monads, they are generally implemented in a stack safe way. In the case of strict monads, we can always implement tailRecM. What is an example of a monad we want to support that is not strict, yet not stack safe? I don't know of one.
Meanwhile, since authors can't depend on tailRecM, they use recursive flatMaps, and ask for
Monad
, when in reality they need something stronger: a Monad that supports recursive flatMaps or small enough inputs that you don't blow the stack.To me, I'd rather tax implementers a bit more, or force some Monads into
FreeT
or such to get tailRecM than to deal with stack overflows due to the monad you are using (say for Xor) not supporting recursive flatMap.The text was updated successfully, but these errors were encountered: