API naming, StructuredTaskScope

Eirik Bjørsnøs eirbjo at gmail.com
Fri Nov 8 13:26:25 UTC 2024


Hello,

I've been making some effort to reconcile my excitement about structured
concurrency as a concept with my surprising lack of enthusiasm looking at
code expressed using the API suggested in the JEPs.

I think a lot of it comes down to naming. Yes, I know naming is hard, and
too easy to bikeshed about. But I also think it's important to get it
right. APIs using well chosen names at the right abstraction level are more
pleasurable to use.

(Disclaimer: The questions here are of mostly rhetorical nature, to prove a
point or demonstrate confusion. I'm not necessarily looking for direct
answers)

Ok, let's try to pick apart the name "StructuredTaskScope":

Is it a scope?
Maybe, maybe not. It seems to me that the actual "scope" here is the
lexical scope -  a language concept, not necessarily an API construct.
Using the word "scope" in the API construct isn't wrong, it just doesn't
seem to add a lot of value. Everything in programming seems to either be a
scope or be in a scope..

Is it a task?
It seems so. It's a piece of work which can be divided into smaller tasks
which can be executed concurrently, then completed as a unit.

Is it structured?
Sure. However, will we ever have a TaskScope which is _not_ structured? If
not, "Structured" does not add much value, other than as a marketing term.
I feel this will not age particularly well. Could we move it to the package
name, API docs, somewhere else?

Now, let's look beyond the STS name and look into what we can actually _do_
with an STS:

scope.fork(() -> ..)
Since we're forking a scope, you may think this returns another.. scope?
Ok, it doesn't. Then perhaps..  a Task? No. A SubTask! Well, then what's a
Task? There is no such thing! Well, ok..

scope.join():
What does it actually mean to "join a scope"?  Is "join" used in a
transitive or intransitive sense? Is something in the scope being joined
(tasks?), or are we (the caller) "joining" the scope, like Thread::join?
Maybe the API docs give us a clue: "Waits for all subtasks started in this
scope to complete". Aha, so we are "completing" the scope/tasks/subtasks.
Then perhaps "join" is here just to provide symmetry with "fork" and
"complete" would be a more honest name?

Would something like the following be an improvement?

public Result performTask() {
>     try (TaskScope scope =  TaskScope.open()) {
>         Task<String>  task1 = scope.task(() -> query(left));
>         Task<Integer> task2 = scope.task(() -> query(right));
>         scope.complete();
>         return new Result(task.get(), task2.get());
>     }
> }


Or perhaps get rid of "scope" completely?

public Result performTask() {
>     try (Task task =  Task.open()) {
>         Subtask<String>  task1 = task.subtask(() -> query(left));
>         Subtask<Integer> task2 = task.subtask(() -> query(right));
>         task.complete();
>         return new Result(task.get(), task2.get());
>     }
> }



When introducing new APIs with new abstractions it's quite common that the
initial naming of constructs ends up too.. abstract. While working on
the API's implementation, it's also easy for implementation details to leak
into the API naming. I have a feeling this may be going on here. Are we at
"peak abstraction"?

Thanks,
Eirik.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20241108/b472c45c/attachment.htm>


More information about the loom-dev mailing list