diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index b6cd352ba66..220068ba4c6 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -379,11 +379,38 @@ object Foldable { loop() } + /** + * Implementation of [[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, Iterator[A])) => M[Either[(B, Iterator[A]), B]] = { case (b, it) => - if (it.hasNext) M.map(f(b, it.next))(b1 => Left((b1, it))) + val go: B => M[Either[B, B]] = { b => + if (it.hasNext) M.map(f(b, it.next))(Left(_)) else M.pure(Right(b)) } - M.tailRecM((z, it))(go) + M.tailRecM(z)(go) } }