-
-
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
lazy foldM
#1030
Comments
Sounds exciting. |
@eed3si9n that's a good question. I'm currently re-reading http://functorial.com/stack-safety-for-free/index.pdf and thinking about whether/where |
I wasn't able to come up with an implementation that is both stack-safe and able to terminate early. What is yours (given that you have I was only able to imagine one if the return type of Maybe the current I think |
@TomasMikula I don't think you can create a general solution that satisfies both requirements via def foldM[G[_], A, B](it: Iterator[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = {
def loop(z: B): Eval[G[B]] =
if (it.hasNext) Eval.defer(G.flatMapEval(f(z, it.next))(loop))
else Always(G.pure(z))
loop(z).value
} So I guess that |
I meant to include in my last comment that the Stack Safety for Free paper specifically mentions |
@ceedubs Can your implementation terminate early, e.g. for an infinite |
Intuitively, the decision about termination needs to be made by
None of these is the case in your implementation, so I wonder... |
@TomasMikula yeah as long as the override def flatMapEval[A, B](fa: Option[A])(f: A => Eval[Option[B]]): Eval[Option[B]] =
fa match {
case None => Now(None)
case Some(a) => f(a)
} If we use the
Once we return Having said all of that, I don't think this is a very satisfying solution. Even if we were to add a |
@TomasMikula I pushed the code I was playing around with here. |
Yeah, but can you terminate early and return something else than |
@TomasMikula not when working with |
@ceedubs that doesn't seem very satisfying indeed. You basically use the monadic effect for early termination, which means 1) you cannot use the monadic effect for something else, and 2) So why not use |
@TomasMikula You should be able to use I say that specifically with regard to I was playing around locally, and it's pretty straightforward to create a stack-safe |
Makes sense. I didn't mean to impose |
Given this signature of @typeclass trait FlatMapRec[F[_]] extends FlatMap[F] {
def tailRecM[A, B](f: A => F[A Xor B])(a: A): F[B]
}
@typeclass trait MonadRec[F[_]] extends FlatMapRec[F] with Applicative[F] this could be the implementation of def foldM[M[_], A, B](z: B, as: List[A])(f: (B, A) => M[B])(implicit M: MonadRec[M]): M[B] = {
def go(x: (B, List[A])): M[(B, List[A]) Xor B] = x match {
case (b, Nil) => M.pure(Xor.right(b))
case (b, a::as) => M.map(f(b, a))(b1 => Xor.left((b1, as)))
}
M.tailRecM(go)((z, as))
} |
@TomasMikula nice - thanks! I was scratching my head trying to figure out how to do things with |
Yeah, I don't think we can generalize this to any @typeclass trait Uncons[B, A] {
def uncons(b: B): Option[(A, B)]
} EDIT: which is essentially the same as Regarding your |
Resolves typelevel#1030, though it may be desirable change the default implementation to be lazy in the future. This can be done with `FreeT` but as of now I'm not sure how to do it without.
Resolves typelevel#1030, though it may be desirable change the default implementation to be lazy in the future. This can be done with `FreeT` but as of now I'm not sure how to do it without.
Foldable.foldM
is a really handy function. For example, the following code will sum the input numbers until the sum reaches 10. Once 10 is reached, it will return aLeft
, or if 10 is never reached, it will return aRight
with the final sum.Unfortunately, in its current form, it will eagerly evaluate the entire input stream, because it's implemented in terms of
foldLeft
. You can see this if you change(1 to 20).toStream
toStream.continually(1)
.The initial proposal of
foldM
in #356 was lazy in consuming the input list (good), but wasn't stack safe (bad).I've played around with this a bit, and I've noticed that you can implement a
foldM
that is both lazy and stack-safe given anIterator[A]
and aMonad
that implements something likedef flatMapEval[A, B](fa: F[A])(f: A => Eval[F[B]]): Eval[F[B]]
. However, the only way you can implementflatMapEval
in terms offlatMap
is by calling.value
on anEval
, so thisfoldM
instance ends up not being stack-safe for types that don't overrideflatMapEval
.I suspect that the best answer to this issue is to define a more constrained
Monad
type and require it. It's possible that one with theflatMapEval
definition I mentioned earlier would work. I suspect something like #616 might be a better solution.The text was updated successfully, but these errors were encountered: