-
-
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 for "Iterables" #1414
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,6 +173,12 @@ import simulacrum.typeclass | |
|
||
/** | ||
* Left associative monadic folding on `F`. | ||
* | ||
* The default implementation of this is based on `foldLeft`, and thus will | ||
* always fold across the entire structure. Certain structures are able to | ||
* implement this in such a way that folds can be short-circuited (not | ||
* traverse the entirety of the structure), depending on the `G` result | ||
* produced at a given step. | ||
*/ | ||
def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = | ||
foldLeft(fa, G.pure(z))((gb, a) => G.flatMap(gb)(f(_, a))) | ||
|
@@ -372,4 +378,39 @@ object Foldable { | |
Eval.defer(if (it.hasNext) f(it.next, loop()) else lb) | ||
loop() | ||
} | ||
|
||
/** | ||
* Implementation of [[Foldable.foldM]] which can short-circuit for | ||
* structures with an `Iterator`. | ||
* | ||
* For example we can sum a `Stream` of integers and stop if | ||
* the sum reaches 100 (if we reach the end of the `Stream` | ||
* before getting to 100 we return the total sum) : | ||
* | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> type LongOr[A] = Either[Long, A] | ||
* scala> def sumStream(s: Stream[Int]): Long = | ||
* | Foldable.iteratorFoldM[LongOr, Int, Long](s.toIterator, 0L){ (acc, n) => | ||
* | val sum = acc + n | ||
* | if (sum < 100L) Right(sum) else Left(sum) | ||
* | }.merge | ||
* | ||
* scala> sumStream(Stream.continually(1)) | ||
* res0: Long = 100 | ||
* | ||
* scala> sumStream(Stream(1,2,3,4)) | ||
* res1: Long = 10 | ||
* }}} | ||
* | ||
* Note that `Foldable[Stream].foldM` uses this method underneath, so | ||
* you wouldn't call this method explicitly like in the example above. | ||
*/ | ||
def iteratorFoldM[M[_], A, B](it: Iterator[A], z: B)(f: (B, A) => M[B])(implicit M: Monad[M]): M[B] = { | ||
val go: B => M[Either[B, B]] = { b => | ||
if (it.hasNext) M.map(f(b, it.next))(Left(_)) | ||
else M.pure(Right(b)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry if this was not clear from my previous comment. |
||
} | ||
M.tailRecM(z)(go) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we also mention here in the doc the incentive of this special implementation for
Iteratable
s?