-
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
Conversation
32333ad to
c577845
Compare
49bbb0c to
914bdb5
Compare
| (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 comment
The 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:
scala> new Array[Any](100).asInstanceOf[Array[String]]
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
... 36 elided
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.
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 comment
The 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?
scala> def test[A](a:A):Array[A] = new Array[Any](1).asInstanceOf[Array[A]]
test: [A](a: A)Array[A]
scala> test(1)
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [I
... 28 elided
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.
Isn't is caused by specialization? There is even a test that uses foreachPar on a List[Int]: https://github.com/hanny24/scalaz-zio/blob/35bd04f28cedeb3c988a83af206c37d48d3b58ad/core-tests/shared/src/test/scala/zio/ZIOSpec.scala#L70
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.
Yeah saw that test now. I have to admit that I don't understand how this works. It clearly does though :)
| c <- ZIO.uninterruptibleMask { restore => | ||
| for { | ||
| as <- ZIO.traverse(as)(a => ZIO.interruptible(fn(a)).fork) | ||
| _ <- as.view.zipWithIndex.foldLeft[ZIO[R, E, _]](ZIO.unit) { |
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.
The call to .view here seems unnecessary, or am I missing something?
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.
I did not want to materialize it. But if we change to ZIO.traverse we will need to materialize it anyway.
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.
Rewritten in a way that uses ZIO.traverse_
| as <- ZIO.traverse(as)(a => ZIO.interruptible(fn(a)).fork) | ||
| _ <- as.view.zipWithIndex.foldLeft[ZIO[R, E, _]](ZIO.unit) { | ||
| case (io, (f, idx)) => | ||
| io *> f.await.flatMap(arbiter(as, promise, buffer, todo, idx)).fork |
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.
An alternative to this is to combine this step with the ZIO.traverse(as)( ...) call above, so that one less fiber per element in as is created.
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.
I don't think it actually that easy to combine it: we need to be able to interrupt all fibers from arbiter helper method.
| 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 comment
The 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 comment
The 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.
| (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 comment
The reason will be displayed to describe this comment to others. Learn more.
| buffer <- UIO.effectTotal(new Array[Any](size).asInstanceOf[Array[B]]) | |
| buffer <- UIO.effectTotal(Array.ofDim[B](size)) |
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.
Also, it's probably best to move the buffer into a Ref.
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.
What is gain of keeping mutable array in Ref? I think that reference will never change, so Ref#modify will never discover that value was changed.
Secondly - we know that each fiber operates only on it's index, so that are really independent operations.
What can go wrong?
Re: ofDim - I understand this gives better performance for primitive types but requires ClassTag[B] - do we want to change method signatures?
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.
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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in #1937
| e => promise.halt(e).unit *> Fiber.interruptAll(fibers), | ||
| a => | ||
| todo.modify { t => | ||
| buffer.update(idx, a) |
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 buffer a Ref[Array[B]] will help here.
Partially solves #1182, missing
raceAllandforkAll.Inspired by #1424