-
Couldn't load subscription status.
- Fork 1.4k
issue-1182: foreachPar, foreachPar_, collectAllPar, reduceAllPar #1773
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
Changes from all commits
c577845
e6865a4
3986e6b
914bdb5
35bd04f
7df8206
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2022,29 +2022,78 @@ private[zio] trait ZIOFunctions extends Serializable { | |||||
| * | ||||||
| * For a sequential version of this method, see `foreach`. | ||||||
| */ | ||||||
| final def foreachPar[R, E, A, B](as: Iterable[A])(fn: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = | ||||||
| as.foldRight[ZIO[R, E, List[B]]](effectTotal(Nil)) { (a, io) => | ||||||
| fn(a).zipWithPar(io)((b, bs) => b :: bs) | ||||||
| } | ||||||
| .refailWithTrace | ||||||
| final def foreachPar[R, E, A, B](as: Iterable[A])(fn: A => ZIO[R, E, B]): ZIO[R, E, List[B]] = { | ||||||
| def arbiter( | ||||||
| fibers: Iterable[Fiber[E, _]], | ||||||
| promise: Promise[E, List[B]], | ||||||
| buffer: Array[B], | ||||||
| todo: Ref[Int], | ||||||
| idx: Int | ||||||
| )(res: Exit[E, B]): ZIO[R, Nothing, Unit] = | ||||||
| res.foldM[R, Nothing, Unit]( | ||||||
| e => promise.halt(e).unit *> Fiber.interruptAll(fibers), | ||||||
| a => | ||||||
| todo.modify { t => | ||||||
| buffer.update(idx, a) | ||||||
| if (t == 1) promise.succeed(buffer.toList).unit -> 0 else UIO.unit -> (t - 1) | ||||||
| }.flatten | ||||||
| ) | ||||||
|
|
||||||
| (for { | ||||||
| size <- UIO.effectTotal(as.size) | ||||||
| todo <- Ref.make(size) | ||||||
| buffer <- UIO.effectTotal(new Array[Any](size).asInstanceOf[Array[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. Will this not cause a java.lang.ClassCastException ? A quick experiment from the scala repl: 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. I think the trick here is not to cast it to specific type, but only to a generic one. 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. I still don't get how this works at runtime though, because the array will need to get a concrete type at some stage right? 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. Isn't is caused by specialization? There is even a test that uses 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. Yeah saw that test now. I have to admit that I don't understand how this works. It clearly does though :) 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.
Suggested change
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. Also, it's probably best to move the buffer into a 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. What is gain of keeping mutable array in Re: 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. Perhaps answer for my first question is memory barrier. |
||||||
| promise <- Promise.make[E, List[B]] | ||||||
| c <- ZIO.uninterruptibleMask { restore => | ||||||
| for { | ||||||
| as <- ZIO.traverse(as)(a => ZIO.interruptible(fn(a)).fork) | ||||||
|
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. 🤔 Can we look for a solution that only traverses the structure once and forks one fiber per element? ZIO.traverse(as.zipWithIndex){ case (a, i) => fn(a).flatMap(arbiter(...)).fork }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. For sure! 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. Yes, in #1937 |
||||||
| _ <- ZIO.traverse_(as.zipWithIndex) { | ||||||
| case (f, idx) => | ||||||
| f.await.flatMap(arbiter(as, promise, buffer, todo, idx)).fork | ||||||
| } | ||||||
| _ <- promise.succeed(Nil).when(as.isEmpty) | ||||||
| c <- restore(promise.await).onInterrupt(promise.interrupt *> Fiber.interruptAll(as)) | ||||||
| } yield c | ||||||
| } | ||||||
| } yield c).refailWithTrace | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Applies the function `f` to each element of the `Iterable[A]` and runs | ||||||
| * produced effects in parallel, discarding the results. | ||||||
| * | ||||||
| * For a sequential version of this method, see `foreach_`. | ||||||
| */ | ||||||
| final def foreachPar_[R, E, A](as: Iterable[A])(f: A => ZIO[R, E, _]): ZIO[R, E, Unit] = | ||||||
| ZIO | ||||||
| .succeed(as.iterator) | ||||||
| .flatMap { i => | ||||||
| def loop(a: A): ZIO[R, E, Unit] = | ||||||
| if (i.hasNext) f(a).zipWithPar(loop(i.next))((_, _) => ()) | ||||||
| else f(a).unit | ||||||
| if (i.hasNext) loop(i.next) | ||||||
| else ZIO.unit | ||||||
| } | ||||||
| .refailWithTrace | ||||||
| final def foreachPar_[R, E, A](as: Iterable[A])(f: A => ZIO[R, E, _]): ZIO[R, E, Unit] = { | ||||||
|
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. This function is duplicating a lot from the foreachPar method, maybe this could be rewritten to leverage that function instead? 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. I know it's possible, I just wanted to avoid allocation of the extra array with results. |
||||||
| def arbiter( | ||||||
| fibers: Iterable[Fiber[E, _]], | ||||||
| promise: Promise[E, Unit], | ||||||
| todo: Ref[Int] | ||||||
| )(res: Exit[E, _]): ZIO[R, Nothing, Unit] = | ||||||
| res.foldM[R, Nothing, Unit]( | ||||||
| e => promise.halt(e).unit *> Fiber.interruptAll(fibers), | ||||||
| _ => | ||||||
| todo.modify { t => | ||||||
| if (t == 1) promise.succeed(()).unit -> 0 else UIO.unit -> (t - 1) | ||||||
| }.flatten | ||||||
| ) | ||||||
|
|
||||||
| (for { | ||||||
| size <- UIO.effectTotal(as.size) | ||||||
| todo <- Ref.make(size) | ||||||
| promise <- Promise.make[E, Unit] | ||||||
| c <- ZIO.uninterruptibleMask { restore => | ||||||
| for { | ||||||
| as <- ZIO.traverse(as)(a => ZIO.interruptible(f(a)).fork) | ||||||
| _ <- ZIO.traverse_(as) { f => | ||||||
| f.await.flatMap(arbiter(as, promise, todo)).fork | ||||||
| } | ||||||
| _ <- promise.succeed(()).when(as.isEmpty) | ||||||
| c <- restore(promise.await).onInterrupt(promise.interrupt *> Fiber.interruptAll(as)) | ||||||
| } yield c | ||||||
| } | ||||||
| } yield c).refailWithTrace | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Applies the function `f` to each element of the `Iterable[A]` in parallel, | ||||||
|
|
@@ -2092,12 +2141,7 @@ private[zio] trait ZIOFunctions extends Serializable { | |||||
| * composite fiber that produces a list of their results, in order. | ||||||
| */ | ||||||
| final def forkAll[R, E, A](as: Iterable[ZIO[R, E, A]]): ZIO[R, Nothing, Fiber[E, List[A]]] = | ||||||
| as.foldRight[ZIO[R, Nothing, Fiber[E, List[A]]]](succeed(Fiber.succeed[E, List[A]](List()))) { (aIO, asFiberIO) => | ||||||
| asFiberIO.zip(aIO.fork).map { | ||||||
| case (asFiber, aFiber) => | ||||||
| asFiber.zipWith(aFiber)((as, a) => a :: as) | ||||||
| } | ||||||
| } | ||||||
| foreachPar(as)(v => v).fork | ||||||
|
|
||||||
| /** | ||||||
| * Returns an effect that forks all of the specified values, and returns a | ||||||
|
|
@@ -2258,7 +2302,7 @@ private[zio] trait ZIOFunctions extends Serializable { | |||||
| final def mergeAllPar[R, E, A, B]( | ||||||
| in: Iterable[ZIO[R, E, A]] | ||||||
| )(zero: B)(f: (B, A) => B): ZIO[R, E, B] = | ||||||
| in.foldLeft[ZIO[R, E, B]](succeed[B](zero))((acc, a) => acc.zipPar(a).map(f.tupled)).refailWithTrace | ||||||
| collectAllPar(in).map(_.foldLeft(zero)(f)).refailWithTrace | ||||||
|
|
||||||
| /** | ||||||
| * Returns an effect with the empty value. | ||||||
|
|
@@ -2308,8 +2352,8 @@ private[zio] trait ZIOFunctions extends Serializable { | |||||
| final def reduceAllPar[R, R1 <: R, E, A](a: ZIO[R, E, A], as: Iterable[ZIO[R1, E, A]])( | ||||||
| f: (A, A) => A | ||||||
| ): ZIO[R1, E, A] = | ||||||
| as.foldLeft[ZIO[R1, E, A]](a) { (l, r) => | ||||||
| l.zipPar(r).map(f.tupled) | ||||||
| a.zipPar(collectAllPar(as)).map { | ||||||
| case (head, tail) => tail.fold(head)(f) | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
|
|
||||||
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.
That's an effect which should be captured, making
bufferaRef[Array[B]]will help here.