Trio: Should we rename nurseries?

Created on 18 Apr 2018  ·  81Comments  ·  Source: python-trio/trio

OK, let's do this.

Question: Should we rename "nurseries"? It's something that keeps coming up, and while it would be somewhat painful to change now, it'll be pretty much impossible within the next 6 months... so I guess now is the time to have a discussion, and if we decide not to change it then this can at least become the canonical issue that we link people to when they ask about it.

The most common version of this complaint is something like: "ugh, nursery is so cutesy. What about TaskGroup, or WaitGroup, or TaskScope, or something like that?" I don't find this version of the complaint compelling, so let me start by explaining why that is.

Names like TaskGroup, or TaskScope are excellent names for ordinary classes, the kinds of name we invent every day. For that kind of class, you want something that's descriptive, because you have a lot of them and people don't spend much time with any given one. And of course you want to follow the conventions, so the camelcase itself is informative: even if you aren't familiar with the particular class in question, you at least can tell at a glance that it is a class.

Nurseries, though, are a different kind of thing entirely. They're a new fundamental concept: you have to learn them up front, like you have to learn about for loops, and functions, and the difference between an object attribute and a local variable. Once you've used them for a few days, they recede into the background as part of your basic vocabulary. And they're just different from the concepts most people already know, so trying to give them a familiar descriptive name is actually misleading: you have to look them up and learn them. So, I want a name that feels like a primitive, like "for" or "function" or "variable" – something short and lowercase. And if anything, something that's a bit opaque and unfamiliar is probably better, because it signals "hey, new concept here". You can't have a lot of concepts like that, but nurseries are carrying an entire programming paradigm on their shoulders, so I think 1 new word is within our budget.

And regarding it being "cutesy": This doesn't really bother me, given that (1) this is a domain where "reaping orphan zombie children" is already considered totally ordinary technical language, and (2) after you use nurseries for a few minutes the name stops feeling cutesy, just like you stopped noticing that "trees" have nothing to do with botany, "functions" often don't function (little debugging joke there), "threads" have nothing to do with textiles, etc. So like... ok, it's a bit cutesy but whatever, people will get over it. And being cutesy actually has some upside: the whole joke about "a nursery is where child tasks live" probably does help it stick in people's minds. OTOH, I'm not like super attached to having a cutesy name either; I think it's mostly a neutral attribute.

So that's why I'm convinced that "nursery" is better than TaskGroup or similar.

But...... when talking to @graydon about this today, I did have some doubts about "nursery", for two reasons. The first is, he pointed out that "nursery" is also used as jargon for generational garbage collectors (to refer to the youngest generation, which often gets special handling). So a language implementer might ask "is this task in that nursery?", and "is this Task in the nursery?" and those are two completely unrelated questions. Fortunately it's mostly implementers who encounter the GC version of nursery, but still... this is a legitimate collision.

And the other is, I realized that the name is actually a bit less apropos since #375 landed and we tweaked the nursery semantics. Originally, I really wanted to emphasize the idea of "supervision", because we had the whole "parenting is a full time job" rule where the parent task had to park itself at the end of the nursery block or exceptions wouldn't propagate and stuff. Now the supervision part has dissolved into becoming an implicit part of the runtime, and doesn't really exist as a concept at all anymore – it's just, like, exceptions propagate themselves, what is there to talk about. The code inside the nursery block is just a slightly-special child. So that part of the metaphor has become a bit weird.

So here's another idea: maybe we could use a name that emphasizes that this is a place where your call stack splits/branches/words like that. Like, async with trio.open_split(), or open_branchpoint, or something like that? With the metaphor being that first you make a note "here's a point in my call stack where child tasks might split off", and then calling start_soon actually attaches a new branch there. I'm imagining like a hinge or a hook or something.

Anyway, that's just one idea – if y'all have other ideas I'd be interested to hear them, and ditto any thoughts on how the "nursery" name worked for you as either a newcomer to Trio or as you've become more experienced.

design discussion potential API breaker

Most helpful comment

While I kind of like nursery, just for the heck of it, here's my random proposal:

If parenting isn't a full-time job anymore … maybe it's actually fun? Like a party?

So let's trio.start_party() and invite tasks to party together w/ party.invite(task) ?

All 81 comments

The nursery name works fine for me. It's well explained in the tutorial, and I liked the cuteness! 😄

split and branchpoint work too, but how would you call the resulting object? I think nursery.start_soon(fn) works slightly better than split.start_soon(fn)? Or is it because I'm used to nurseries?

Anyway, I'm not personally opposed to a change, as it would be easy to adapt to a new name.

What do you think of childminder?

To me it has a lot of the same helpful properties as nursery:

...but isn't (to my knowledge) overloaded with another programming meaning, and also directly refers to the existing-programming-concept "child" in the name, and so maybe avoids some of the cutesiness that some people have found distracting?

That said, I'd be fine with keeping nursery and fine with a change too. \

While I kind of like nursery, just for the heck of it, here's my random proposal:

If parenting isn't a full-time job anymore … maybe it's actually fun? Like a party?

So let's trio.start_party() and invite tasks to party together w/ party.invite(task) ?

Not sure about open_split. The word "split" is overloaded, and so doesn't necessarily invoke any obviously-intended associations immediately. Further overloading an overloaded term can add to cognitive load. It also doesn't signal "learn this important foreign concept!" to me as much.

The term "branchpoint" is better than "split" on both counts. But both "split" and "branchpoint" give up direct association with the concept of "children".

I'm perfectly fine with the nursery name as is.

@apexo You still can't leave the party. You (as the "host", i.e. the nursery's initial task) can just decide to do your own thing while the party is ongoing, just like every other party guest.

My preferred name, should we decide to switch the name, would be the rather boring "task manager", or taskmgr.

That being said, I'm also OK with keeping the name. "Branchpoint" … well, maybe. @jab it does for me.

Personally: when I came across this nursery thing, I thought, OK, new name, new concept I actually need to think about instead of assuming that I already know what it's about. Which is exactly what we need to get across.

I didn't know about nurseries WRT garbage collection. However our nursery is very obviously not about GC, so I don't think that in practice there's much confusion. I'd be more wary about a name like branch since in if FOO then BAR else BAZ, BAR and BAZ are different branches of execution – a concept which (a) many people have already come across and (b) is in roughly the same realm, so more danger of confusion.

I like nursery

And the other is, I realized that the name is actually a bit less apropos since #375 landed and we tweaked the nursery semantics.

I think the name is still valid even after #375.
Before this change we could see the main coroutine which open the nursery as the nurse which will take care of the child-coroutines.
After the change the main coroutine is just a lazy parent who drops it children-coroutines to the nursery and go have a beer do it own work.

Attempting to make a positive contribution (browsing thesaurus looking for new names is a favourite pass-time anyways) I might suggest:

  • supervision-related:

    • custodian
    • steward
    • caretaker
    • porter
    • attendant
    • concierge
  • childcare-related:

    • creche
    • cradle
  • tree/splitting-related:

    • outgrowth
    • bifurcation
    • partition
    • branch
    • sprout <-- personal fave
    • offshoot
    • bough
    • limb
  • organization-division related:

    • subsidiary
    • office
    • branch, also
    • division
    • department
    • chapter <-- has a nice "part of a story" secondary resonance

My issue with options like sprout/bough/limb is that those sound like they refer to the children, while we need a name for the point where the children are attached.

@smurfix has a good point about "branch" already being overloaded, and as a control flow thing, so that's definitely confusing.

Of the nursery alternatives... maybe "splitpoint" is the best I can come up with so far? (As in, "this is a point where control can split".) It has some resonance with "checkpoint", which is our other bit of funny terminology. Something like:

async with trio.open_splitpoint() as splitpoint:
    splitpoint.start_soon(...)

I actually kind of like how this de-emphasizes the concept of "children", since trio doesn't really reify tasks/threads/whatever-you-wanna-call-them. There are no task ids or task objects or anything (unless you go digging into trio.hazmat). OTOH we still need to refer to the different concurrent functions in conversation -- I suppose we could call them "splits", like: "While the first split is doing name resolution, the other splits are waiting...". Maybe that's too weird?

("sproutpoint" is also an option, but that might be too silly even for me :-).)

I suppose there's also

async with trio.open_family() as family:
    family.start_soon(...)

but I feel like "family" is too commonly used as a kind of generic word for "category". "This family of concurrency frameworks uses families...", ick.

kindergarten

@dhirschfeld returns to lurking

Seriously though, I thought nurseries was a little cutsie at first, but it didn't really bother me [1]_.
Now, I actually think it's a very good name and better than any alternative I've seen / thought of.

.. [1]: Full Disclosure: I use a data processing library called "pandas" on a daily basis and that also doesn't bother me! 😜

My problem with splitpoint or similar pointy names is that a point, conceptually, is just there. Like in geometry, it doesn't do anything.

My personal favorite still is a task manager. Maybe with trio.manage_subtasks() as mgr: await mgr.start(…).

My problem with splitpoint or similar pointy names is that a point, conceptually, is just there. Like in geometry, it doesn't do anything.

That would be the point though :-). (No pun intended.) When you type open_splitpoint, you'd be marking a place in the call stack where you could potentially hang off other children/sprouts/whatever:

f1 -> f2 -> (splitpoint) -> f3

This doesn't actually do anything. Then later, when you call start_soon, that's when something actually happens:

f1 -> f2 -> (splitpoint) -> f3
                       |
                       +--> f4

Well, in geometry you (mostly) don't just arbitrarily draw a point – the point exists as a consequence of something else (like two lines intersecting). Afterwards, other things exist as a consequence of the point being there (like a line being defined by two points). In both cases the "active" parts are the non-points.

shrug maybe I'm overthinking this … on reflection I'm like -0.1 on splitpoints-or-whatever, -1 on branchwhatever, and +0.1 on keeping nursery.

To me, splits/branches/sprouts suggest the point where things go different ways but not so much that they eventually merge. When thinking in spacial terms they might be the same thing, especially if you're used to visualize tasks in a tree, but it doesn't translate well to the idea of concurrency (multiple things running at the same time over parallel lines).

Maybe, given our peculiar semantics, it makes more sense to call the nursery a _merge/join/muster point_ and use _splitting/branching/forking/sprouting_ to refer to what now is the nursery.start operation.

The idea of supervision is lost but, well, so it is in the paradigm of calling/returning functions (either sync or async) which in some ways is a close relative to this. Instead of waiting for one function call to return or raise, you're waiting for many concurrent ones. What's so special about that? 😉

splits/branches/sprouts suggest the point where things go different ways but not so much that they eventually merge. ... it doesn't translate well to the idea of concurrency (multiple things running at the same time over parallel lines).

This is why I highlighted above the term chapter as having both organizational-structure gloss and a part-of-a-sequential-book gloss: the more I think about this the more I think it might be a good (mixed) metaphor. In the org-chart-sense it's definitely part-of-a-hierarchy. But in the book sense it's a block-structuring element of a _sequential story_ that introduces-and-concludes a bunch of specific scenes, and maybe lets a couple threads of the plot connect-up with those from earlier or later chapters.

It also includes (unlike all the options here -- including nursery!) the concept of conclusion, as you say. The term evokes both "beginning of chapter" and "end of chapter" -- things started and things wrapped-up -- in almost equal measure.

I like my concurrency libraries like I like my concrete. Bland and functional. TaskManager

To use it in a sentence:

"The TaskManager manages its tasks."

Inessential weirdness and all that.

A common/boring name for a collection of jobs is a batch.

If you want a name that reflects some control-flow analogy...

Collections of threadlike things bound together at both ends are

  • bundles
  • fascicles (okay, that's weird, but maybe? They're hierarchical bundles of bundles of.., which is pretty cool)
  • sheaves (maybe confusing if someone knows the mathematical definition of sheaf, which I don't)

Places that things leave and then return to

  • nest (keep the cutesy!)
  • camp

Portmanteau silliness: daglet

chapter really works for me. It's short, concise, and builds on a well-known literary concept with an emphasis on beginning and ending, with stuff happening in between. It doesn't assume anything about the nature of the story--whether child labor or worker robots--and it makes sense to talk about passing a chapter around to functions, e.g. "Which chapter [of the overall book/app] am I in?".

This is fun! :)

Some more:

  • track
  • passage
  • stage
  • phase
  • portion
  • hive

I have no issues with nursery, this is a python library and playfully named libraries are commonplace.

Anyway, nobody is shocked in this context about parents, children, (and infanticidal moves as killing children!), nursery is just a little blip that stops felling weird after you have seen it 12 times.

One analogy that has not been raised yet is the hub/spokes, but I am not fully convinced myself...

I like the name "nursery". If we choose a new one, I'd hope that it share some of the properties of "nursery" that make me like it:

  • not in common usage elsewhere (as mentioned, maybe we can do even better than "nursery" in this regard, but I don't think the GC term overloading is a substantial issue)
  • short and easy to type (I think seven characters is substantially better even than the ten in "splitpoint")
  • doesn't invite ambiguous abbreviations (mgr could be a ThingManager for just about any value of Thing, and a large program might have a lot of those)
  • reads well in the trio pattern of open_thing() being a function that returns a context manager that scopes the lifetime of a new thing
  • not _too_ cutesy (I think "party" might be going a bit too far in that direction)
  • suits the context in which it's used reasonably well - it doesn't have to be obvious without an explanation, but the explanation should make sense

I'm not sure it makes sense to switch for a name that's only infinitesimally better than "nursery", since there are switching costs. I don't feel like any of the names proposed so far are substantially better than "nursery", though I do like several of them.

Some random further thoughts:

  • "custodian" is in common usage in Scheme, I think, and refers to a sort of scope for _all_ resources - when a custodian is shut down, all files created in its scope are closed, threads are killed, etc. IMO it's similar enough to the nursery idea to make the differences a potential point of confusion for Schemers.
  • The unit-of-an-organization meaning of "chapter" doesn't jump out at me nearly as much as the book-subdivision one does, so it feels like a mixed metaphor - the instinctive objection is "my program isn't made of chapters, that's for books! programs are made of (modules|packages|functions|...)". Nursery doesn't have this problem because nobody talks about dividing things into nurseries, in any domain.
  • I quite like "hive" and "nest", and they're very short and non-overloaded (as long as no one tries to write a trio library for controlling a certain trendy home thermostat...), but I think they score worse on "likely preexisting associations link to correct concepts" than nursery does.

I don't really mind the nursery naming. If we do change it I wonder about a transportation analogy. Buses/trains/airlines tend to have hubs or central dispatches that buses/trains/planes leave from and eventually come back to. Something along those lines has the connotations of management, coordination, or authority.

Just an aesthetical subjective opinion from an amateur philologist, I would call them 'beacons' or 'cocoons' if you want to keep a biological-like sounding. If you want a more techie-industrial-crafting narration, maybe 'foundries' or 'workshops' it is more appropriate. A potentially helpful note, the whole library's concept and the arrows-like diagrams in your post made me think much about 3D-printing.

Thanks for your work.

The name nursery feels very dynamic and apt. It represents progress (growth) and chaos that is being constantly monitored on behalf of the parent process, which was responsible for the creation or the split. The metaphor is great - parent can just let the nursery manage the process while they take care of their work, with the how's of nursery being a black-box. Also, the name nursery stands out in the excellent blog post as something familiar w.r.t concurrency in everyday life, yet something I would want to read more about in the context of Trio. Irrespective of the simplifications in the implementations, the core idea behind a nursery resonates almost immediately for a first time reader and makes it approachable to take their first steps.

A coworker of mine suggested spool.

One could make the obvious connections with threads/threading and, if we stretch it, (sp)awn p(ool).

One problem with the name "nursery" is it only conveys that this is a place where new tasks can be spawned. But the really important thing about a trio nursery as I understand it, and in particular the thing that distinguishes it from other libraries' APIs for launching concurrent tasks, is that it also bounds the execution of all the tasks launched within it. That is, the nursery doesn't exit until all its child tasks have exited. The "nursery" metaphor doesn't convey this, in fact quite the opposite: most things born in nurseries grow up and leave the nursery to live their adult lives (especially if the nursery is working well and not prematurely killing it's charges :-) ).

I would favor a name that covey's this "boundedness", that no task started by this object will outlive that object. But I don't really have a good suggestion. I like variants of "scope", but @njsmith already rejected "TaskScope". I also like "lifetime", but that doesn't have the whimsy of the current name.

Here's a maybe off topic question though. Does there need to be a nursery object at all? Since the boundary provided by a nursery is the same as the async with statement (which is a lexical scope that doesn't need a name) couldn't trio just keep track of the current "nursery" (or whatever it's called internally) for the current task, and then let any async function start new tasks using global functions (e.g. trio.start and trio.start_soon)? It would seem to me the only change this would require compared to the current behavior is to have a top-level nursery created for the trio.run call itself, which would bound the execution of all tasks started within that top-level call. You would still be able to open new nurseries to create bounded groups of tasks within larger tasks, but you wouldn't need to name a local variable for the resulting object nor pass that variable to other functions, so the name would be much less important.

@maffoo - giving the nursery object a name makes it possible to give another task the ability to start tasks in your scope, even though it is not in that scope, by passing the other task the nursery object. This provides additional flexibility in more intricate cases, which I wouldn't want to do away with. (It may even be required by trio's internals, I'm not sure.)

@oremanj, I could imagine this being useful in some cases, but if those are special "intricate" cases perhaps it would be useful to support the basic default case of not capturing the nursery in a local? (Although explicit is better than implicit and all that :-) .) Also, out of curiosity, could you point me to a place where this used?

The more important reason for reifying the nursery is that we want to be able to see by looking at the source code all the functions that don't have access to it. Any function call that isn't passed a nursery can be assumed not to have access to any nurseries that outlive the call. If nurseries were implicitly attached to the current task then we'd lose this.

@njsmith and @oremanj, makes sense, thanks for the explanations. I still think a name that draws attention to the boundedness of subtasks would be nice, but as @njsmith says, the ability to spawn new work is a special capability in its own right, so a name with that emphasis is ok too.

I had a couple pals point me to projects after sending @njsmith's recent nursery/goto blog post.
I figured I'd stick them here in case they spur further (naming) insight though these names are already mentioned in the initial description.

Personally I like nursery and I think it it communicates the idea of a task with a scope or lifetime.

Only serious names I can think of offhand are task_branch() or open_branch():

with trio.task_branch() as branch:
    branch.start_soon(coro, args)

I find the whole API to be very verbose:

with trio.open_nursery() as nursery:
     nursery.start_soon()

If you do a lot of async, you will type this very often. So we need to make it convenient. For this particular bit, it's ok to make it a shortcut, as if it's the main idiom, people will see it often and look it up anyway. It's not like it's going to be some obscure call you see once in a while.

So, just because nursery is long, I'm for changing it.

If you like the nursery analogy and the branching analogy, we can satisfy both using "nest", which is also short:

with trio.nest() as nest:
     nest.hatch(foo)
     nest.hatch(bar, 1)

But I wouldn't mind to do even shorter.

We could make it a verb, using __call__ to provide a shorhand:

with trio.runner() as run:
     run(foo)
     run(bar, 1)

Or give it a native look and feel with a dunder method:

with trio.scope() as tasks:
     tasks << foo
     tasks << bar, 1

It's shorter to type than start_soon(), and convey that something special happens.

I also like the name scope because it's neutral. We already have many different kind of scope, so you now you need to learn "scope of what ?" to understand it. But you already understand it place boundaries on something.

Also we could just focus on the causality, which is in the end the whole benefit of trio:

with trio.timeline() as line:
     line.run(foo)
     line.run(bar, 1)
with trio.begin() as scope:
     scope.run(foo)
     scope.run(bar, 1)

Also, this is a common task, so a shortcut to do "run this bunch of task by blocking" maybe in order.

await trio.collect(foo, bar) # like asyncio.gather, but with the nursery guaranty

Seems like scope and nest are winning right now. But since scope is already well understood as a generic way to define "_binding something to a delimited region_", if think using it would ease people into the concept.

After all, we have many scopes already (lexical scope, dynamic scope, angular scope...) so programmers know the general idea, but also that the specific application of it need to be looked up.

I find:

with trio.scope() as scp:
     scp.run(foo)
     scp.run(bar, 1)

Quite clear. And even as a one liner for the most common case:

await trio.scoped(foo, partial(bar, 1)) 

The kid in me also like the __lshift__ oriented api since it is a bit shorter to type and feels a bit native, like a go operator or something. But I understand that people may feel it's too much magic.

Because I gave try at a working POC of porting nurseries to asyncio (https://0bin.net/paste/V5KyhAg-2i5EOyoK#dzBvhdCVeFy8Q2xNcxXyqwtyQFgkxlKI3u5QG0buIcT), I discussed with Yuri yesterday since he is going to implement nurseries in uvloop, and eventually on asyncio. asyncio won't use that because capturing ensure_future() results would break a lot of things, but maybe something with a warning.

Anyway you can see in the test code at the bottom that "scope" reads pretty well and I find the intent well conveyed. The class could be called something like AsyncTaskScope, but aliased as trio.scope

My point is, we need to solve the naming before Yury get serious about it. He hasn't started yet, but he works fast. And once he codes it as "nursery" in uvloop, which is currently the only name he knows and uses for the concept, it will be set forever.

I really don't think sticking with a 7 letters word that we will need that often is a good thing, especially since the IT vocabulary doesn't lack expressiveness already.

I approve of renaming nurseries. When I first looked at Trio I had a little trouble understanding how nurseries worked, and I think that was mostly because of the name. The problem is not that the name is cute, it's that it doesn't have the right connotations. The name "nursery" suggests "a place where tasks/coroutines start out". Compare with memory management, we use the name "nursery" to mean "a region of memory containing recently created objects".

But Trio tasks don't just start inside nurseries, they spend their entire lifetime inside the nursery. And the most important feature of nurseries is how they handle the end of the lifetime of their tasks: in particular, control doesn't exit the nursery's with statement until all the tasks created in that nursery have finished (either by all of them exiting normally, or by one of them raising an exception and the nursery cancelling the others). It's this property that allows us to reason about code running in Trio, as described in Nathaniel's article "Notes on structured concurrency".

So whatever name is chosen, I think that it would be helpful to beginners if that name hinted at this lifetime (or synchronization/joining) property. Maybe the name lifetime would work.

The corresponding concept in normal (synchronous) structured programming is the "block" and this has a useful double meaning because a nursery's with statement blocks (waits) until all its tasks have finished before exiting. So maybe a name like lifetime block or parallel block or async block would have the right connotations. ("Async block" is a near-rhyme with "basic block", which also has good connotations since it's the unit of code which compilers use to reason about the properties of code.)

I don't like the name "scope" because I associate that with controlling the binding of names (for example in "lexical scope" and "dynamic scope") and it doesn't suggest anything about the lifetime of tasks to me.

I like "lifetime". It conveys the idea pretty well. But it's a bit too long to type for something you will constantly use. I wouldn't want the len() function to be called get_len().

On the other hand, "block" is a dangerous name because in the async world, we are very preoccupied about things blocking or not, and it has a mixed meaning.

We could keep the methode named scope(), but it would return an object of type LifetimeScope.

The name of the concept needn't be the same as the name of the function, just as the length of a list is not the same as the function len.

The name nursery stood out to me at first, and sparked my interest in this project. That's where the "cutesy" thing is actually important, it's important for first time users. nest or lifetime would work for me too, but scope is way too familiar, and doesn't really communicate that this _is_ a new concept

I suggest warrant which is a noun and a verb at the same time. I think it may underlie control notion (but I am not a native Entlish speaker so I may be wrong about that).
Also it may be nice as a native primitive in the future.

warrant:
    do_something()
    do_otherthing()

Otherwise guard or park or farm for the same resasons.

farm:
    do_something()
    do_otherthing()

I understand and approve the reasons for nursery but it does not sound like a primitive name for me (contrary to if, for, while, etc.)

Edit: I also love block, stage, track previously mentionned.

Edit2: Final thought: track sounds like a really appropriate metaphor for concurrent tasks to run, starting almost all at the same time and waiting for all the runs to be finished, before ending. No as familiar as scope for instance, but less abstract than nursery and definitly shorter.

I kind of like the word 'hive'. Every bee works for the good of the hive. And starts and ends it's life as part of the hive. Only with a new queen will some of the workers transfer to a new hive. And you could use 'hive.spawn ()'

Nurseries are groups of tasks that extend from the beginning of the first task and last until the last task finishes. The group of task can be cancelled if one task cannot complete. This is a common concept in scheduling construction projects. Depending of the software (Primavera, MS Project, DynaRoad, etc), scheduling method (CPM, PERT, LBMS) and detail level (project or project portfolio), it is called "summary task", "hammock activity", "subproject" or "project". On Gantt charts, it is usually represented above its subtasks as a thin line with wedges clearly marking its start and finish dates.

Sumtask may better describe that subtasks are executed concurrently. Project may better explain that it can be cancelled if one subtask fails.

In construction, summary activities are often used to gather cost information rather than for scheduling but the concept is the same. For example with MS Project, you can link summary activities (very powerful assuming that you don't link subtasks with a task in another scope).

Since then, I played with the concept a bit further and ended up applying it to asyncio more seriously.

I'm definitely biased to the name "Execution Scope" now. Indeed, the concept of a scope already has the notion of nesting and boundaries, while being well known in programming. Yet putting "execution" in front of it makes it clear that it's a specific kind of scope, and one that limits the execution of something.

This translates into quite an understandable API IMO. Example from the asyncio lib experiment:

import random
import asyncio
import ayo

async def zzz():
    time = random.randint(0, 15)
    await asyncio.sleep(time)
    print(f'Slept for {time} seconds')

async with ayo.scope(max_concurrency=10, timeout=12) as run:
    for _ in range(20):
        run.asap(zzz()) # schedule the coroutine for execution

Not only the implementation works, but it also runs as it reads, which is a great benefit for a new concept.

This allows to build even higher level tools on top of it:

async with ayo.scope() as run:
    run.all(zzz(), zzz(), zzz())
    run << zzz() # shorcut for run.asap(zzz())
    run.aside(callback, foo, bar=1)  # run.asap(asyncio.run_in_executor(None, callback, foo, bar=1))
    run.after(2, callback, foo, bar=1)
    run.at(dt.datetime(2018, 12, 1), callback, foo, bar=1)
    run.every(0.2, callback, foo, bar=1)

"Run that in this execution scope" feels like a clear way to express all those.

scoped threads from rust's crossbeam crate

In languages with RAII (like C++, Rust, D, etc.) the fundamental language feature that enables this is that of a "scope", which is often created with a pair of curly braces { ... }, and invokes the destructors of the objects in the scope in some specified order.

In C++, for example, the simplest way to use a nursery would be to just:

{  // create a new scope:
    auto t0 = std::thread(foo);
    auto t1 = std::thread(bar);
    // ...
}  // t0 and t1 must have finished (or have been handled somehow)

In crossbeam, nursery is actually just called Scope.

I came up with "task scope" independently from reading the opening post. I think it's close to ideal. ("Execution scope" is arguably more accurate, but also more letters.)

When you consider practical usage, it's a clear win over "nursery". In practice you'd like a short name for accessing the nursery, but n is not prudent. Proposed idiom for task scope using ts:

async with trio.open_task_scope() as ts:
    ts.run_soon(...)

Scopes (with whatever modifier—you appear to like long descriptive names much more than I do) should be fine, because what’s being enforced is pure (Algol) stack discipline with regard to spawning children. It’s not just like (dynamic) variable scopes, it’s exactly the same thing.

Perhaps "pod" as peas in a pod. Its short, is not commonly in use, and describes confinement.

I've been toying with the idea of writing a structured concurrency library for Rust. I haven't gotten far enough to see what is actually possible given the existing concurrency primitives, but I'm toying with naming the library everett, after Hugh Everett, the originator of the Everett interpretation, a.k.a. the Many-worlds interpretation, of quantum mechanics. Under this metaphor, the name breach seems like an appropriate alternative to nursery. A breach, as sometimes used in science-fiction, is a wormhole or branch point between two or more worlds. Along these metaphorical lines, it makes sense that a breach would be a branch point from which multiple concurrent threads of execution originate. Taking the metaphor further, instead of run_soon, the method that registers tasks for execution could be called entangle.

Example usage:

async with trio.open_breach() as breach:
    breach.entangle(...)

If you have to add a section to the docs to explain your naming, your naming is not good. Keep it simple.

That's a fair argument against such a naming approach, but I think it needs to be balanced against the desire to implicitly communicate that these are new fundamental concepts that must be grokked.

Here's one:

async with trio.summon_waiter() as waiter:
    await waiter.start(keep_drinks_full)
    sip()
    waiter.start_soon(make_poutine)

It seems to work both literally and metaphorically, hopefully in self-explanatory ways.

Should this issue still be open? If so, I have a drive-by suggestion borrowed from Inform 7: "scene".

The idea is to emphasize that the object stands for the time period in which tasks (actors?) do their work. Actors enter and exit, but the scene is not done until all the actors have performed their roles.

Other possibilities might be "shift" (short for work shift) or "meeting".

None of these are a perfect fit, but it might be good to explore other names for an interval of time.

I'd go with TaskGroup.

Two reasons. (a), anyio uses that name and asyncio IIRC is moving towards that also. Plus you get to use tg as the variable to store a taskgroup in, which is distinctive enough that it works. ("n" or "nu" doesn't work, and "nursery" as a variable or attribute name is just too plain long for my taste.)

More seriously, however, (b) yes this is a novel concept, but at the same time it is not, or at least should not be. Other languages start picking up on the Structured Concurrency concepts, and frankly this is how async code should have worked from the beginning.

Nurseries/taskgroups no longer are novel concepts, they {are | should be | are going to be} the "new normal". You get to name normal everyday ideas with normal everyday names (not cute new names that need to be explained); these concepts are not / no longer special. The names describe them and provide a way to think about them, and that's all there is to it.

Hey, I like "scene"! It's a name that any non-native english speaker like me easily understands and it conveys the time connotation pretty well together with that of "a space where part of the action (of the whole story) takes place", like in this definition (emphasis mine):

A subdivision of an act in a dramatic presentation in which the setting is fixed and the time continuous.

The only limitation is that "actors" outlast the scene... so it's not a perfect fit. But that's a problem that "nursery" already has/had with "children".

There's another name that now I see (with surprise) that was not suggested which has the lifetime and the "confinement" connotations, it's easy to remember and it's perfectly short: "cell"!

What do you think?

Mmmm, maybe it's time to make a poll in the Trio forum... or it's already "too late"?

Well, there is a topic for this. Not sure if it's better here or there. Anyway, I'm just passing by and this is up to someone else.

https://trio.discourse.group/t/the-terminology-bikeshed-thread/95

One complication for scene is that it suggests that tasks are actors... and actors already refer to a specific and very well known approach to concurrent programming :-). As far as I can tell, you have actors when:

  • any task can create new tasks at any point with unbounded lifetime
  • every task has an associated identity and message queue
  • message queues have unbounded buffering

Actors are commonly contrasted with CSP, which makes exactly the opposite choices (task lifetimes are bound to their parent's lifetime, tasks are anonymous, message queues are bounded). And on all of these points, Trio sides with CSP... so using "actors" to refer to Trio tasks creates a lot of potential for confusion :-).

More generally

At some point I need to make a pronouncement here. But pronouncements take a lot of energy, which I've been short on lately, and I think there are a lot of higher priority decisions to make (like finishing nailing down our APIs for processes/channels/listeners/kwargs/etc.). So I'm probably going to let this ride for a while longer. (And hey, maybe the broader structured concurrency community will converge on something in the mean time.)

Having let this simmer for a while, I like TaskScope and trio.open_task_scope(). I have this slight nagging feeling that the analogy to lexical variable scoping isn't perfect, but I can't figure out why, so it might just be perfectly normal paranoia.

@scottjmaddox Because “nurseries” implement dynamic scoping, not lexical = static scoping?

Hmm, I would say that the lexical/dynamic distinction doesn't apply to nurseries at all. That distinction is all about which scopes you consider when resolving an implicit scope lookup, but nurseries are never accessed implicitly, only through explicit references.

@njsmith Oh. You’re right. I think I first thought (and said, somewhere around here) that nurseries implemented dynamic scoping months ago, and during all that time it didn’t occur to me that they don’t, in fact, scope stuff.

I’d like to withdraw my suggestion of _Something_optScope, then.

But unless I’m gravely mistaken (again), nurseries do implement nested task lifetimes, so I’d like to propose _Something_optLifetime or, if that sounds too Rusty, _Something_optRegion. (But Lifetime feels clearer, given that we’re not talking about literal memory regions here.)

I like nurseries, the cuteness is moderately (subjectively) appealing, but this may lend itself to the larger appeal below, and it maintains with the analogy that it contains children (although breaks a bit as the child coro's entire life is in the nursery (death in nurseries! :)).
The biggest appeal is that it doesn't have a namespace collision with the abstract mental stack dev's have
e.g.
'Process' is a bad name it as it already has a vague, abstract definition and dev's could already have other uses in there mental and design model, in and out of programming; I have to process this file as input, this munge process, these sequential steps are a process, design process, etc...
Or worse, if they're developing at a factory and the design spec has 'process' throughout, and they're writing mutli (posix) process code. Every time a dev types, reads or thinks process they have to disambiguate. A small extra mental tax.

Whereas 'thread' is wonderful (thanks Victor A. Vyssotsky!) it has a decent analogy; 'interwoven', 'light', 'thin', 'many together create something larger' but when you read 'thread' in code you know exactly what it is! It was coopted with email, sorry for the devs' of multi-threaded email apps.

Guess what the first threads were call? "TASKS" (OS /360 MVT) another terrible name that would be causing a collision on some of the suggestions already placed here, and if it stuck asyncio would have perhaps avoided using the name 'Task' to avoid confusion with what is now threads.

Working on a project with data streams and io streams I needed a name for a higher order 'data flow pipeline' at first I coopted and reused the name 'stream'. Halfway through I renamed it to 'creek'. I didn't like that it felt 'small and unperformant' but it removed a mound of confusion.

For this reason create names that unique and concrete first, and have a fitting analogy second. Nurseries are good in this way. Yes it's completely foreign at first, but as soon as you learn it's definition, which is necessary as a central concept to curio's async block model, you know it's that curio block thing, never any confusion.

As the nursery model is not specific to curio, perhaps a better fitting and widely adoptable name is out there to be found. Perhaps nurseries are fine, and just have to stick.
Perhaps 'thread' was considered cute or unfitting and had opposition in 1966, but I'm glad it stuck. Likewise for port/stream/stack/queue. All of these were coopted from something physical and are concrete. It's nice when the analogy is very well aligned, but once it's adopted it feels correct.

I wish that abstract names like process, job, and task were left out of the stack of OS's/languages/frameworks so devs could use them without collisions.

@njsmith, you speculated one year ago that a name change could become practically impossible within 6 months or so. Is there still any merit in debating the best/most appropriate name? (If so, do you have an updated estimate on how quickly it would have to happen, if it does?). I know you mentioned two months ago letting it ride "a bit longer" while you figure other things out, but I agree more with your initial reasoning that such a central part of trio should be stabilized ASAP

My thought is that this issue has been open for a year, nothing has come up in that time that's obviously better than nursery (and better enough to justify the cost of switching) and I feel it's increasingly unlikely any such name will be found (I personally favor taskgroup but not enough to argue strongly for it). On the flip side the cost to switch is only increasing.

We might be better off closing thus issue and devoting our collective mental energy in areas with a better chance of having a return on that investment.

to be honest I was a bit irritated at first by the name open_nursery but now I am totally fine with it.
Anyway, I believe in good names and if you are going to rename this you should IMHO use "split_control" since it is what the context manager does:

async with trio.split_control() as split:
    split.start_soon(sum_numbers, 0, 50000000)
    split.start_soon(sum_numbers, 50000000, 100000000)

This discussion has probably run its course.

But after reading over the thread this morning I had an original idea that I think communicates a lot of things at the same time, and also remains true to the whimsy of nursery:

asylum

Similar to a task in an asynchronous part of a program: you generally don't leave the asylum, and lot of crrraaaazy things go on inside.

Should this still be open?

If so, I have four things to say:

  1. Description of my initial reaction to the name "nursery"
  2. An aside, countering the idea of prioritizing shorter identifiers
  3. A suggested rename (which has very short identifiers anyway)
  4. A defense of "nursery".

Initial Reaction to "nursery"

When I first encountered Trio a year or so ago, I found the name awkward to fit for like a literal second, but then I just internalized it and moved on.

For me it was a mentally cheap operation to mutate the meaning of "nursery" in Trio's context from "somewhere that children live" to += " and die".

(Actually I did at least two parallel ways of making it make sense in my mind. The other one that I currently remember is: a child coroutines is always a child, so it can never leave the nursery.)

I maybe experienced subtle brief background irritation at the choice to use a name that was more "cutesy" than it was self-explaining, but once I understood it I could see it as "close enough" to self-explaining and felt fine with it.

My first exposure to structured concurrency was Sustrik's libdill and blog articles, not Trio, so that probably influenced something, but I do not remember debug traces of that cognition.

Aside On The Push For Short Names

I am trying my best to empathize that some people actually feel the extra effort or tedium of typing "long" identifiers starting around seven or eight characters (which is just mind-boggling to me, but I'm trying to stay mindful that experience is relative).

However, I have learned to not consider it even acceptable - let alone good - to optimize code for writing convenience, unless uncompromising priority is given to optimizing for reading.

Code must be optimized at every turn to guide as many future readers as possible to a correct decode. This includes not only its big-picture intent and its business logic but also of every assumption, expectation, and implementation detail which is at all relevant to it working right.

That is not to say that short identifiers are bad - all else being equal, shorter identifiers are strictly better - in fact more readable - than longer ones.

But those are still optimizations for reading. They are emphatically not optimizations for writing.

Rarely have I seen optimization for shortness that did not neglect some aspect of what goes in to making code readable - including being informative, self-describing, and easy to search, navigate, and hop around.

Optimization for writing convenience was almost always a key cognition flow causing that neglect.

If you want or need to type short names for writing convenience, I truly wish you the best experience with that, but please don't inflict code optimized for your writing experience onto other people to read.

That cost should be borne by automated tooling, once, when the code is written or saved or committed, not by every reader of the code who comes later.

Thankfully I'm fairly confident that @njsmith will not trade readability/clarity/self-explanation/guiding-to-correct-interpretation for writability, based on what I've seen of his work so far.

What about fork?

I haven't decided if this is actually better, but I like it, and no one has suggested it:

async with trio.open_fork() as fork:
    fork.start_soon(foo, ...)
    # or maybe even just:
    fork.soon(foo, ...)
    ...

The biggest advantage is that the code that a nursery covers is a fork in the execution paths - the spot where a linear call flow forks into a call tree.

The biggest disadvantage is that the word "fork" is already used in Unix-y programming for forking a new process.

Now I have rarely needed to refer to that as "the fork" or "a fork" - in those cases I usually say "child", "forked child", or "forked process", or naturally use the verb "fork" in a phrase like "the process forks" or "forks a process" - so I don't think the collision is too bad. Even in a codebase that does both, the terminology naturally lends itself to disambiguation: "forks a coroutine", "the coroutine forks a new process", "by forks a coroutine or forks a process?", etc.

It's also not ideal for searchability or for signaling that there is a new concept, but a good point was made that as structured concurrency gains ground, it will just be a standard concept.

"Fork" does not imply the boundedness that structured concurrency makes its central tenet - forked processes outlive their parents all the time. But if unstructured concurrency goes the way of the goto, we won't need to disambiguate that. The "for loop" term does not try to encode or hint at the exclusion of unstructured jumps into or out of it - it is simply a feature of a language to make that impossible.

Anyway, I like start_soon a lot already, but I can defend fork.soon, because as a verb, "fork" already implies starting the execution path, and "soon" retains the important reminder that it will be scheduled later, once permitted.

(I also considered: fork.branch but people above mentioned that "branch" already has a strong and nearly universal association in programming to mutually exclusive execution paths; fork.prong but it loses the "soon" reminder; fork.tooth but I think people are less likely to recognize it than "prong"; and fork.tine but that's just even more obscure.)

Also, the physical object that maps to the word, a fork, has a clearly defined end. A fork's prongs stay attached to its body - it takes some effort and force to break a fork's prong off its body, and it's an obviously bad idea which leaves you with a broken fork.

We could even start calling unstructured concurrency as "fork breaking": like

  • "wow that code was so hard to follow, it had broken-off forks everywhere" (that just sounds bad even if you don't know what it is, which is a good property for stuff like this), or

  • "hey Bob, good code, just one thing to fix before we can merge: you broke off one of the ends of the fork over here, let's think of a way to do this with structured concurrency instead."

Nursery is Fine

Honestly, if you just left it as "nursery", I wouldn't be bothered.

A key advantage that I haven't seen explicitly mentioned is that it is searchable - that nursery does not collide with any existing programming term is not just helpful to human cognition: it also helps searching, whether in some code with a dumb text find or in a search engine, etc.

I mean sure, you can add "structured concurrency" to a search engine to narrow things down, but that's only if you already know what to look for. Imagine an unfamiliar newbie trying to look up "programming nursery" after hearing about the concept vs "programming {split,fork,tree,task group}" or whatever.

I was thinking more about the above fork suggestion, and I've updated my last comment with some of these thoughts, but I want to elaborate on some of those additions:

"Fork" does not imply the "boundedness" that structured concurrency makes its central tenet - forked processes outlive their parents all the time. But if unstructured concurrency goes the way of the goto, we won't need to disambiguate that. The "for loop" term does not try to encode or hint at the exclusion of unstructured jumps into or out of it - it is simply a feature of the language that such a thing is impossible.

I think the key thing here: "nursery" is optimized for a world where structured concurrency is new, a world where it needs to be discovered and searched for and distinguished because it is beset on all sides by unstructured concurrency.

What would we name it in a world where structured concurrency is the way? If unstructured concurrency was simply unavailable or even unconceivable to most programmers, even treated with knee-jerk bigotry by some in its more limited forms, like the modern goto?

I'm reminded of some of the discussions I've seen on @njsmith's "Notes On Structured Concurrency". I've seen at least one person respond to it by saying "well, a function call is still a jump to anywhere", not seeing that it's a distinctly more limited jump that can be expected to go only to well-defined entry-points and to always come back out of the same spot. Presumably because they've never had enough reason to mentally step through code able violate that expectation, so their mind simply does not automatically conceptualize such code flows vividly and clearly enough for them to fully connect and feel the weight of the comparison.

If the world was similar with structured concurrency, does that change the way we think about what the best name should be?

Would my idea about optimizing for searchability make as big of a difference? Would the need for a distinctly new term make as big of a difference? Probably not.

At that point I wouldn't even worry about "'fork' does not disclaim the association with how a forked child process can outlive its parent", because by then operating systems and every library would provide robust primitives that prevented child processes from outliving their parents too, and "oh actually a forked process/thread/coroutine can outlive its parent if you [old deprecated API]... it's a backwards compatibility thing from back in the day, in a modern system you'll never run into it" would be the only way a typical person even learns that it is possible.

(Aside: I think we can actually implement structured concurrency at the OS process level already, using things like process groups and sessions, which have existed since old UNIX days on every UNIX-y system. It's just that there are no libraries or CLI interfaces in widespread usage that do this, as far as I know.)

Anyway.

When we get to that point, "nursery" will stand solely on its association to the concept of children, and from that word the association to the concept of child coroutines.

Let's look at it this way:

What do you call the block of code inside the async with trio.whatever() as whatever2?

Now does that term still work if it was a built-in language feature, with dedicated syntax, like whatever { /* my coroutine forking code here */ }?

One of the reasons why I think I'm drawn to my "fork" suggestion is that it generalizes. I spend enough time dealing with multiple programming languages that I've gotten used to looking for terms and wording habits I can reuse regardless of language.

If I needed to refer to that spot of code verbally, without referring to a specific implementation's word choices, I think I'd call that the "fork block" or even just the "fork" - if I needed more context I'd say something more verbose or specific once per conversation to pin it down, and then thereafter use "fork".

Why? Because I could keep reusing that term no matter what language I switched to, or even if the underlying way of concurrency was coroutines or threads or processes.

Now maybe that's a rare and irrelevant use case. I usually do use the terminology of the language/library/whatever I'm dealing with in context.

Also, I just realized (or more like remembered or even more like consciously pinned down): even in the absence of a culture where structured concurrency is the norm, a "fork" brings up a nice physical analogy, which I've gone ahead and edited into my prior post but which I'll quote here for linear reading flow:

Also, the physical object that maps to the word, a fork, has a clearly defined end. A fork's prongs stay attached to its body - it takes some effort and force to break a fork's prong off its body, and it's an obviously bad idea which leaves you with a broken fork.

We could even start calling unstructured concurrency as "fork breaking": like

  • "wow that code was so hard to follow, it had broken-off forks everywhere" (that just sounds bad even if you don't know what it is, which is a good property for stuff like this), or

  • "hey Bob, good code, just one thing to fix before we can merge: you broke off one of the ends of the fork over here, let's think of a way to do this with structured concurrency instead."

That analogy will be weaker for people who deal enough with Unix-y programming to strongly associate "fork" with splitting processes, but the terminology is still unambiguous per my earlier remarks, and Windows-y programmers or programmers who stick to higher level APIs will be untainted and free to benefit from the association/analogy/visual.

Anyway, returning to my point:

When we get to the point that structured concurrency is the norm, if "nursery" is not enough of a stable optimum, it will simply get evolutionarily outcompeted. People will start calling it something else, whatever is more mentally easy and accessible, until eventually one of those alternatives catches on to the point that its usage hits critical mass.

For example, we already do use a general language-neutral term, not for this exactly, but in the general vicinity of it, a more general concurrency concept: call tree.

It also implies boundedness, the same way that "call stack" does - a child function can't just re-parent itself to a different spot in the call tree just like it can't re-parent itself up the call stack (semantically, anyway - tail call optimization or trampolines may avoid a traditional call stacks/trees at the implementation level).

But it doesn't map as well to Trio's API "shape"... best I can think of right now is something like:

async with trio.call_tree() as call_tree:
    call_tree.branch_soon(foo, ...)
    # or maybe
    call_tree.fork_soon(foo, ...)
    # or maybe even `as call` above and then:
    call.soon(foo, ...)

And that's not ideal. Furthermore each nursery only represents a branch point or... ahem, fork... in a program's call tree, not a full actual call tree, because it does not contain or represent any of the nurseries opened below/within it.

Of course "fork" was my attempt to extract the same meaning that "branch" gives in the context of talking about call trees into an independently stable form.

So we can already see the process in action, which once again finally brings me back to my original point:

At some point, a new term will catch on, if "nursery" isn't "good enough".

The term nursery can live on past that point for a while in code which keeps using Trio or other libraries which have the term wired into their API, and those working with such code will get used to the exchange "what's a nursery?" "oh it's just this library's term for {task group,fork,split,call tree}", until enough pull requests or issues or forks or alternative libraries build up expressing humanity's want for a different term.

And that's probably okay. It's okay to just wait for that, wait for "humanity to decide", wait for natural selection to bring the most currently human-friendly term to the most widespread usage.

It might not get us the best terms, but it'll get us terms, whether or not we try to come up with our own. And usually the terms are alright. Usable enough. Precise enough and unambiguous enough in context, etc.

On the other hand, trying to talk about it is us participating in that process - picking a better word is influencing the process. In theory, a convincing argument here, or a change in naming in Trio, could help spread a given terminology that needs just that edge to win.

Which is fine - but the question is:

Are we at that point yet? Because if we're past the point where structured concurrency itself has reached memetic and cognetic critical mass, then we need terminology that works best then. Until then, we might as well stick with nurseries, because I think it has clear advantages in searchability and unfamiliarity and so on, and just wait and see what catches on. Once something does, it will eventually be obvious, assuming Trio is still flourishing and relevant to the wider programming community.

And if the change has to happen later, backwards compatibility is not exactly hard - for example if open_nursery gets renamed to fork, just open_nursery = fork at the bottom of the top-level trio import, and if we don't want to carry that around indefinitely (I know, it is a heavy burden) we have SemVer for that, and Python is so dynamic that someone maintaining a legacy codebase (but for some reason not just pinning their pip dependencies to specific versions even though they're unwilling to go through and update their code) can just do trio.open_nursery = trio.fork just under their import trio.

Most Important Part

In the meantime, I think where it really shakes out is conversational usage - how easy it is to say, understand, remember, etc, and in so far as misunderstandings matter, how resistant it is to nuance decay, double illusion of transparency, etc.

Now that I've thought of "fork" and "fork breaking" and so on, I'm going to be test-driving those when talking specifically about structured concurrency or code spots that use it or fail to use it (as opposed to places where "call tree" is sufficient), and seeing if I run into problems or misunderstandings with it.

I encourage others to do the same. I'd say "may the best memes win", but the "best" memes will win, no may about it.

On further thought: any terminology needs to fit nicely with the "you have to pass the nursery as an argument to the coroutine to create children that outlive it".

Nursery is fairly nice for that:

  1. "nursery" is exclusively a noun - unlike "fork" it doesn't pull double duty as a verb.

    Compare: "the nursery", "the fork", "the fork object".

    (Poor "split" pulls triple duty as an adjective, so even adding a noun to it to make it unambiguously a noun doesn't save it from sounding weird: "the split object".)

  2. More importantly: you can put things into a nursery - namely children.

Why does this matter so much?

Because a key feature of the nursery primitive is passing a nursery through code to let that code put
child talks into that nursery.

I think per the usage point above, we should check all alternative names against that pattern. For example, these seem to flow well

  • "oh you need to pass the {nursery,lifetime,scope,task group} you want the task to live under through to where you want to start it from", or

  • "the reason we want to explicitly pass a longer-lasting {nursery,lifetime,scope,task group} instead of implicitly is so we can know when we might be creating something that has effects or leaks beyond the scope of the block".

Notably, my earlier suggestion of "fork" really gets clumsy around here, and I think this reveals it as deficient in a way that those other alternatives aren't.

Edit:

I mean I guess "you need to pass the fork object you want the new coroutine to fork from" and "we want to explicitly pass an an outer fork object so we can make code that breaks off forks externally visible" kinda works, but it took me more effort to proactively think it up, which isn't a good sign.

The word "branch" should not be reserved for non-concurrent branches, no matter how strongly associated it is with that right now.

A branch is a branch is a branch, regardless of whether it is traversed in parallel or exclusively or not at all.

The call tree visual/metaphor is very valuable for thinking about structured concurrency - and trees have branches!

We should fight for an equal share of that word.

So there are exclusive branches, and there are concurrent branches.

Structured programming brought order to exclusive branches. Structured concurrency brings order to concurrent branches.

The more I think about it, the more "split point" feels right as the term for what a nursery is, maybe not for Trio but definitely for structured concurrency as a whole.

It ties really well with the call tree metaphor and visual:

  1. Picture a call tree.
  2. Look at one of the points where it splits.
  3. Boom. Notice how naturally those words fit. But we're not done:
  4. What are you doing when you pass a nursery down the call tree?
  5. You are letting that code split off from the same point in the call tree!

If I'm teaching the idea of structured concurrency to someone, or at least the passing nurseries part, I want to set up the idea and visual of a call tree anyway - this terminology comes for free.

So "split" is the action, "split point" is the object.

The addition of "point" does a great job of making it a noun: it is natural once we introduce split point objects as references to a specific point where the call tree splits, existing for the purpose of letting other code split more concurrent branches from the same point.

It's short. "split" is one syllable, five phonemes, five characters. (Any complaints about "while" being too long?)

It's free. As far as I know, the word "split" is not used for any one established programming concept, and no mainstream language uses "split" as a language keyword. Certainly the term "split point" is not in use (except for drill bits, apparently).

Yes "split" is generic (but "split point" isn't, and that compensates), but I'm imagining what this should be named in a world where it is a fully established fundamental programming concept, equal with if and while, taught in introductory classes, and in that world, it should be a generic word, because if we do this right, soon enough breaking off a concurrent branch will be just as inconceivable as a goto from inside one function into the middle of another.

I am more excited about this as a generic structured concurrency term proposal than I am for it in Trio - don't care if Trio just keeps calling them nurseries (see my first comment), but if I had to try to apply this to Trio, I imagine something like this isn't too bad:

async with trio.split() as split_point:
    split_point.start_soon(foo, ...)
    # People who want short could do `as sp`!

Also in languages like C or Go, a split point can be represented by a pointer to an object of a type called "split", and that feels like the right alignment of naming between implementation details and abstract concept.

P.S. The entire time I was writing this comment, I kept thinking "I feel like maybe someone said this before and maybe I was subconsciously influenced by it?" - so just before hitting submit I just searched the thread and apparently I've just reinvented the terminology that @njsmith first proposed in the very first comment two years ago. But I had totally forgotten, and arrived at it again from a completely different direction: while thinking about how I would add structured concurrency as a language feature to something like C. So I think there is something more universally true to it.

Thing is, when I first read @njsmith's "split point" suggestion I didn't like it either, and did not think it fit Trio's API shape well.

To the best of my ability to remember.

I clearly didn't like it enough, because I completely forgot about it consciously.

But this also reveals that I did not dislike it strongly enough, and did not identify any bad enough problem with the term itself, because I would have remembered that by now.

@smurfix mentioned it not signaling a new, unfamiliar concept strongly enough, and I agree with that as a circumstantial reason, but I think as structured concurrency gets going, that rapidly goes from being a downside to being an upside.

When structured concurrency was new and unfamiliar, a new and unfamiliar and opaque term was good. Once it is established and normal, a simple term that implies it is the simplest and most familiar and most intuitive concept in the world, like "if", is more fitting.

Anyway, I think it is very notable that I did not like or remember the terms "split" and "split point" initially, and did not think they were a particularly great fit for Trio's API shape, but then rediscovered them while thinking about the best terms for a completely different language.

(Reminder: I'm not trying to push for removing the name nursery right now - see my first comment, and also my previous one, and also remember that it is trivial and not necessarily a bad thing for Trio to keep the nursery name for backwards compatibility when it does decide to adopt "split" and "split point".)

I had occasion to try using both "split point" and "fork breaking" in conversation the other day, when trying to talk about structured concurrency, with a call tree diagram on a whiteboard.

Two very noticeable observations:

  1. "branch point" (which I think @njsmith also suggested in the same early post he suggested "split point" in) is better than "split point", unless someone else has anecdata to the contrary:

    I keep slipping into saying (and typing) "switch point" instead of "split point", and don't even notice it at first.

    Not sure if there is a more fundamental reason beyond the term just not fitting into the patterns of words my brain is used to, but I misspoke so consistently I just announced I was switching to "branch point" mid-conversation, and it went smoothly from there, without loss of clarity.

  2. "branch breaking" was much less "out-of-the-way" and less awkward to say than "fork breaking".

    It felt like introducing "fork breaking" spontaneously into the speech flow was adding a new term, and one which due to unexpectedness might not even immediately phonetically parse to someone.

    I could tell that in order to mitigate this, I would have to say something like "because alternatively we could call a branch a fork", which in context just seemed like it would be an arbitrary and unjustified word-hop from "branch" to "fork" for no good reason to the audience, and thus not particularly memorable, since memory is so dependent on things systematically sensibly fitting in together.

    So after trying to use "fork breaking" once, I just accepted the natural flow of statements like "or in unstructured concurrency, the branch can also be broken off".

I've also found that the distinction and terminology of "concurrent branch" vs "exclusive branch" takes me all of one sentence to establish and was accepted as self-evident without any resistance or confusion the one time it came up in conversation where I could observe the reaction.

The moment I accepted "branch" as the more natural term than "split" to use, I started also finding myself wanting to contemplate whether or not there is some deeper value that could be extracted by reifying the branch point of an exclusive branch (the moment an if or while or switch statement opens) the same way that reifying the branch point of a concurrent branch into a nursery has produced value.

This is getting a bit too esoteric, some weird language design theory direction abstracting beyond structured concurrency as a whole even, but I find myself wondering what we can make of this.

Because it's very interesting that structured programming manages to make do without reifying either the branch execution or the branch point, and yet every structured concurrency design I like reifies either the branch point (Trio's nursery) or the branch execution (libdill's coroutine handle).

One of these days I'm going to just make an account on the structured concurrency forum @njsmith started and relocate all these thoughts to there, but for now streaming them here is just much more accessible of a workflow for me.

The above having been said, I think split is still the more right language keyword than branch, the same way that if and switch are better language keywords than branch.

Structured Concurrency's nursery is Structured Programming's jmp_buf.

This analogy might not stretch far enough to be useful, but here's what I mean by:

Structured Concurrency's nursery is Structured Programming's jmp_buf.

I said in one of my last comments that since modern structured concurrency had reified the concurrent branch point (nursery), it was interesting to think about if there was any value to reifying structured programming's exclusive branch points.

After I thought that, I kept trying to figure out why structured programming managed to make do without any explicit reification of branch points.

I think this is why. It did exist, at first, as a partial controled escape hatch in languages like C. Like a nursery, a C jmp_buf has to be set up higher up in the call tree, and it had to be passed to the code that wanted to branch off of it.

Of course, we could implement everything (if, while, for, switch, break, continue, return, raise, yield, etc) with some clever combination of longjmp, function pointers, and mutable state.

But we discovered these narrower limited and common variants of code jumps, and built them into our languages. Probably something like that exists for structured concurrency. Certainly there is a "shape" difference between commonest case of creating a new nursery to branch off some concurrent stuff at that spot of the call tree, and the less common case of passing around a nursery from elsewhere so that code in one part of the call tree can branch off of a point elsewhere on the tree.

The place where this analogy might break down to the point of uselessness is that maybe the difference between the reified concurrent branch point (nursery) and primitives like split wait and split race and whatever else we discover might not be big enough to justify an almost-vanishing of the reified branch point, but we already empirically know that the difference between the reified exclusive branch point (jmp_buf) and primitives discovered for structured programming is big enough.

Anyway, that's just what I've been mulling on, and it connects to the question of "should we rename nurseries" because if this really is a jmp_buf analogue, then the exact name won't matter at all in the historical big picture that's coming, because a few less flexible primitives will fill most usecases.

I think the important way to check if this is where structured concurrency is going is to look really, really hard at the cases where the "pass a nursery around" actually occurs, and think really, really persistently about how it might be done elegantly with only more limited local-only concurrency primitives like wait and race, or what primitives would be needed to do it.

I think this is important to really try to do, because when we can see the low-level commonality, the fundamental principle, it is easy to keep mentally reaching back to that as "well obviously this one thing covers this too". From the vantage point of seeing the one elegantly unifying thing, it can be a lot harder to find or even look for the few less unifying things that are good enough for almost every case. wait and race are definitely not enough, but they seem like they cover about as much ground as for and if - maybe structured concurrency just needs a couple more structure-breaking primitives like return/continue/break or raise/except or yield.

Conversely, it still seems worthwhile to keep doing the opposite - looking for if there is some way for a reified exclusive branch point to elegantly and usefully replace all of those structured programming primitives. If we find such a way, it might inform the nursery discussion in the other direction, by revealing that nurseries really are here to stay as the primitive of structured concurrency.

(By the way, if anyone wishes to quote or discuss the comments I make here in other places like the Structured Concurrency Trio subforum even if I am not there to participate - feel free, I encourage it.)

Well, this thread has been going since mid 2018, so I doubt nurseries are every going to change name.
If it ever does, I'll add my voice to the crowd asking for something succinct and boring like trio.scope, which is clear enough, short, has no surprise, it's already wildly used and known for communicating boundaries and lifetime.

I like the nursery term. Modulus the GC ambiguity, it is unique, which is what you want for a new programming concept.

I also like the split term. It conveys the semantics in the name itself. Something that nursery doesn't quite achieve IMHO. I concur with the others about split being a bit overloaded already.

I propose rift. I find that it both:

  • Conveys the semantics nearly as well as split does.
  • Is a unique term in the software world.

Anyhow, maybe it's a moot point at this point in time. nursery works.

That's my two cents.

How about mutual as a name ?

Edit: I have no horse in this race, nursery is fine.

Edit 2:
I think its not a bad word, as cancellation is mutual between them.

They have a mutual agreement to continue until any one is cancelled then all are cancelled.

Or it can be short for "mutual cancellation" or similar.

At this point, is there any reason to keep this open? Nursery seems fine and there is no ambiguity there. Also, over 2.5 years after the announcement, I think it will be extremely difficult to change this now. @njsmith

Was this page helpful?
0 / 5 - 0 ratings