Maybe a page can be taken from JS Promise static methods [1]: * Joiner.all() instead of Joiner.allSuccessfulOrThrow() * Joiner.allSettled() instead of Joiner.awaitAll() * Joiner.any() instead of Joiner.anySuccessfulResultOrThrow() * Joiner.until(..) instead of Joiner.allUntil(..) [not part of the JS Promise static functions, but seemed like a logical extension to the naming pattern]
As a side note, perhaps having Joiner.allSettled() return the subtasks themselves (in a stream?), and you can interrogate each one for success/failure, rather than having to manually keep track of subtasks for later use. // Paul [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#static_methods On Thu, Sep 25, 2025 at 5:38 PM David Alayachew <[email protected]> wrote: > Lol, and maybe this too. > > 3. throwWhen(BiPredicate<R, Subtask<T>>) > > On Thu, Sep 25, 2025, 6:36 PM David Alayachew <[email protected]> > wrote: > >> Whoops, minor modification. >> >> 1. cancelWhen<BiPredicate<R, Subtask<T>>> >> >> >> On Thu, Sep 25, 2025, 6:34 PM David Alayachew <[email protected]> >> wrote: >> >>> Makes more sense. >>> >>> You could make a Joiner.Builder class with the following methods. >>> >>> 1. cancelWhen(Predicate<Subtask<T>>) >>> 2. returnValue(R, BiConsumer<R, Subtask<T>>) >>> 3. throwWhen(Predicate<Subtask<T>>) >>> >>> And the absence of any of the above methods has the following default. >>> >>> 1. Cancel on first Subtask Exception. >>> 2. Return all Subtasks as a List. >>> 3. Throw the first Subtask Exception. >>> >>> This gives you the power of permutation, while also give you the Pit of >>> Success. Furthermore, you can do away with all of the static methods. >>> >>> The only weakness is that you leave performance on the table, in some >>> cases. Plus, maintaining the Builder gets a little harder, but not by much. >>> >>> >>> On Thu, Sep 25, 2025, 4:58 PM <[email protected]> wrote: >>> >>>> >>>> >>>> ------------------------------ >>>> >>>> *From: *"David Alayachew" <[email protected]> >>>> *To: *"Remi Forax" <[email protected]> >>>> *Cc: *"loom-dev" <[email protected]> >>>> *Sent: *Thursday, September 25, 2025 2:47:02 PM >>>> *Subject: *Re: StructureTaskScope joiner naming >>>> >>>> Hello Rémi, >>>> >>>> > TS.join() always wait, so this is >>>> > confusing because a joiner with a >>>> > name that does not start with the >>>> > name "await" still await. >>>> >>>> The method name join() makes perfect sense to me -- it joins all the >>>> threads. >>>> >>>> And more accurately, it isn't just waiting, it is joining! Remember >>>> that STS with the default semantics (STS.open()) are those that it would >>>> have if we did CompletableFuture.allOf().join(). >>>> >>>> Therefore, to base your name off of waiting instead of joining would be >>>> the wrong semantics. The method name should remain as join() and should not >>>> be called anything else. Especially not await(). >>>> >>>> >>>> I think you misunderstood me, >>>> i was criticizing the name of the joiners, not the name of STS.join(). >>>> >>>> Some joiners starts with the prefix "await", some don't, but they all >>>> wait, because waiting is something done by the STS implementation, not by >>>> the joiner implementation. >>>> >>>> [...] >>>> >>>> >>>> >>>> How do you feel about my naming suggestions instead Rémi? I don't have >>>> a good replacement for allUntil's name, and I don't want to use cancelWhen, >>>> since it throws out the naming convention. But otherwise, how do you feel >>>> about it? >>>> >>>> >>>> I obviously prefer mine :) >>>> i.e, I think "all" and "await" should not be parts of the name, "await" >>>> as discussed above and "all" because the fact that you want all the subtaks >>>> whatever their states is a separated concern. >>>> >>>> Rémi >>>> >>>> >>>> On Thu, Sep 25, 2025 at 2:20 AM Remi Forax <[email protected]> wrote: >>>> >>>>> So currently we have those joiner defined in the JDK: >>>>> - allSucessfulOrThrow() >>>>> - anySucessfulResultOrThrow() >>>>> - awaitSucessfulOrThrow() >>>>> - awaitAll() >>>>> - allUntil(predicate) >>>>> >>>>> There are several issues, >>>>> - TS.join() always wait, so this is confusing because a joiner with a >>>>> name that does not start with the name "await" still await. >>>>> If you take a look to the doc, the prefix "await" seems to be used >>>>> to convey the idea that the result of the STS.join() is null, >>>>> i.e the joiner does not stores the forked subtasks in a list. >>>>> >>>>> - The other issue is the naming of awaitAll(), which is the shorter >>>>> name, so people will be droven to use it by default, "it's the simpler", >>>>> but unlike the other joiners, it does not cancel the other subtasks >>>>> in case of failure, which is not the semantics you want by default. >>>>> The name seems to imply that because it does ends with "OrThrow", it >>>>> means that there is no cancellation. >>>>> >>>>> - "allUntil" is a half-lie, it will correctly cancel the other subtask >>>>> when one task validates the predicate but at the same time, >>>>> STS.join() will returns all subtasks, not the ones until the >>>>> predicate is true. >>>>> The name "allUntil" only make sense if you see it as the >>>>> concatenation of two orthogonal behaviors, "all" meaning STS.join() >>>>> returns >>>>> all subtasks and "until" meaning stop when the predicate is true. >>>>> >>>>> I propose this renaming (in order): >>>>> - allSuccessful() >>>>> - anySuccessful() >>>>> - sucessfulVoidResult() >>>>> - noCancellationVoidResult() >>>>> - cancelWhen(predicate) >>>>> >>>>> After that, i think we can be a little more fancy and see the fact >>>>> that the implementation returns all subtasks as a separate concern, >>>>> thus enable composition: >>>>> - sucessful().all() >>>>> - anySucessful() >>>>> - sucessful() >>>>> - nonCancellation() >>>>> - cancelWhen(predicate).all() >>>>> >>>>> With all() a default method that override onFork() and result() to >>>>> respectively add each subtask in a list and blindly returns that list. >>>>> >>>>> regards, >>>>> Rémi >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>
