control
Control flows: Command-composing atoms steered by Query parameters.
Module nu.core.flows.control.
Control flows: Command-composing atoms steered by Query parameters.
Nu's Control sub-shape - the Flows that run their bodies under Query
parameters (a condition, an iterable, a count). A Control owns no effects and
yields nothing (VOID): the param slots feed the orchestration, the body slots
carry the writes. param_slots declares which slot indices are parameters;
the rest are bodies. The control_param_is_yielder law holds every param to
a yielding child (Ref / Query / Action) and flow_body_is_mutator holds
every body to a mutating child (Command / Action / Flow).
Loop variables are scoped attrs bindings: ForEachDo / ForRangeDo bind
the current element under a name for one body run - the same
ctx.attrs.let scope Map / Filter use, not a tracked fabric write.
The body is usually a lambda over the element, which mints the name; a plain
body reads it via an attrs ref under an explicit name (itself a child, so it
can be a Literal or a computed Ref).
ForEachParAsync is ForEachDo's fan-out twin: same three args, same
binding, but every element gets its own arm on the loop at once, each on its own
Context branch so the arms cannot stomp each other's loop variable. It lives
here, with the ForEach it mirrors, and borrows the scheduling from parallel.
Its reactive twin, ForEachParReactive, keeps the fan-out open against a
change subscription and lives with the other reactive flows in
nu.core.reactive.
Each atom emits a thunk via compile / acompile and stays immutable -
construction config that must survive with_children lives in payload
(SwitchDo's match keys), never as mutable per-run state.
| Name | Sort | Call | Meaning |
|---|---|---|---|
| Delay | control | Delay(seconds) | Delay(seconds) - sleeps seconds, then continues. No body. |
| DelayedDo | control | DelayedDo(delay, body) | DelayedDo(delay, body) - sleeps delay seconds, then runs body. |
| ForEachDo | control | ForEachDo(items, body, item=None) | ForEachDo(items, lambda x: body) - runs body once per element of items. |
| ForEachParAsync | control | ForEachParAsync(items, body, item=None) | ForEachParAsync(items, lambda x: body) - runs body once per element of items, every arm on the loop at once. |
| ForRangeDo | control | ForRangeDo(start, stop, body, step=1, index=None) | ForRangeDo(start, stop, lambda i: body, *, step=1) - runs body once per value of range(start, stop, step). |
| ForeverDo | control | ForeverDo(body) | ForeverDo(body) - runs body on loop forever. |
| IfDo | control | IfDo(cond, then, else_=None) | IfDo(cond, then, else_=None) - runs then or else_ based on cond. |
| SwitchDo | control | SwitchDo(selector, cases, default=None) | SwitchDo(selector, cases, default=None) - runs the case body keyed by selector's value. |
| WhileDo | control | WhileDo(cond, body) | WhileDo(cond, body) - runs body on loop while cond stays truthy. |
Delay
Delay(seconds) - sleeps seconds, then continues. No body.
Delay(seconds)Path nu.core.flows.Delay. Kind Control, sort control, cardinality void. Arity 1 (1 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
seconds | how long to sleep. |
Notes
- Sync execution blocks on
time.sleep; async execution suspends onasyncio.sleep, so it never blocks an event loop. - No body: for "wait, then run something" use
DelayedDo, or chainDelay(seconds) >> body.
Example
nu.run(nu.Delay(nu.Literal(0.0)))[0] is NoneTrueUndocumented: yields.
DelayedDo
DelayedDo(delay, body) - sleeps delay seconds, then runs body.
DelayedDo(delay, body)Path nu.core.flows.DelayedDo. Kind Control, sort control, cardinality void. Arity 2 (2 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
delay | how long to sleep before body runs. | ||
body | the body to run once the sleep is over. |
Notes
- Sugar for
Delay(delay) >> body; for a bare wait with no body, useDelayon its own. - Sync execution blocks on
time.sleep; async execution suspends onasyncio.sleep.
Example
_ = nu.run(nu.DelayedDo(nu.Literal(0.0), nu.print("done")))doneUndocumented: yields.
ForEachDo
ForEachDo(items, lambda x: body) - runs body once per element of items.
ForEachDo(items, body, item=None)Path nu.core.flows.ForEachDo. Kind Control, sort control, cardinality void. Arity 3 (2 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
items | object | the iterable to walk. | |
body | object | the loop body, run once per element. A lambda over the element, or a tree reading it with Attr(item). | |
item | object | None | the name to bind the current element under, for a tree body. Optional, defaults to "item"; a lambda mints its own. |
Notes
- The lambda runs once, at construction, and gets a ref, not a value: it builds the body and never branches on the element in Python.
- The name belongs to the lambda's code, so nested lambdas each get their own and an inner body still reads the outer element. One lambda used again inside its own body binds the same name, and the inner binding shadows the outer, as with
nu.let; an explicititemkeeps both readable. - The current element is bound for one body run, the same scoped binding
Map/Filteruse. itemis itself evaluated once, before the loop starts, so it can be a computed Ref and not just a literal name.- The binding shadows an outer one of the same name and is released after each run, so the outer value is back once the loop ends.
Examples
_ = nu.run(nu.ForEachDo(nu.Iter([1, 2]), lambda x: nu.print(x)))1
2Nested, each body reading its own element:rows = nu.Iter([["a", "b"]])
_ = nu.run(nu.ForEachDo(rows, lambda row: nu.ForEachDo(row, lambda c: nu.print(c))))a
bA tree body under an explicit name:_ = nu.run(nu.ForEachDo(nu.Iter([1, 2]), nu.print(nu.Attr("n")), item="n"))1
2Undocumented: yields.
ForEachParAsync
ForEachParAsync(items, lambda x: body) - runs body once per element of items, every arm on the loop at once.
ForEachParAsync(items, body, item=None)Path nu.core.flows.ForEachParAsync. Kind Control, sort control, cardinality void. Arity 3 (2 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
items | object | the iterable to fan out over, evaluated once before any arm starts. | |
body | object | the arm, run once per element, concurrently with the others. A lambda over the element, or a tree reading it with Attr(item). | |
item | object | None | the name to bind that arm's element under, for a tree body. Optional, defaults to "item"; a lambda mints its own. |
Notes
- Same three args, the same lambda form and the same binding as
ForEachDo, which is why it sits here rather than inparallel/; the scheduling itself isparallel._scheduling.aeval_foreach_par. - Joins on all, like
Parallel, but over a runtime-sized list:Parallel/Gatherfan out to children fixed at construction, this one to whateveritemsyields at run time. - Async-only, and named for it the way
ParallelAsyncis: every arm is an asyncio.Task on the loop, no threaded placement and no sync path at all. Syncrunrefuses the subtree up front viarequires_async. - It does not take from the concurrency budget. A parked coroutine costs no worker, and these arms are meant never to return, so there is nothing to ration -
max_parallelbounds threads, and this atom uses none. - Each arm runs on its own branch (
Context.branch), soitemis that arm's element and nothing else. The branch shares values by reference, so a live handle in attrs crosses fine, but an arm's own bindings stay in its arm. - Arms that never return are the point: a standing
ForeverDoper element joins only when the surrounding Flow is cancelled. An emptyitemscompletes immediately. - Cancellation propagates: under
Race, cancelling this Flow cancels every arm. First error wins likeParallel, and the remaining arms are cancelled on the way out, since a headless never-returning arm would outlive the Flow that spawned it.
Example
import asyncio
arms = nu.ForEachParAsync(nu.Iter([7]), lambda x: nu.print(x))
_ = asyncio.run(nu.arun(arms))7Undocumented: yields.
ForRangeDo
ForRangeDo(start, stop, lambda i: body, *, step=1) - runs body once per value of range(start, stop, step).
ForRangeDo(start, stop, body, step=1, index=None)Path nu.core.flows.ForRangeDo. Kind Control, sort control, cardinality void. Arity 5 (3 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
start | object | the range's start. | |
stop | object | the range's exclusive end. | |
body | object | the loop body, run once per value. A lambda over the value, or a tree reading it with Attr(index). | |
step | object | 1 | the range's step. Optional, defaults to 1. |
index | object | None | the name to bind the current value under, for a tree body. Optional, defaults to "index"; a lambda mints its own. |
Notes
start,stop,stepandindexare each evaluated once, before the loop starts.- The current value is bound for one body run. Same scoped binding, and the same lambda form, as
ForEachDo. - Follows Python's
rangerules: astepthat never reachesstopfromstartruns the body zero times rather than looping forever.
Examples
_ = nu.run(nu.ForRangeDo(0, 2, lambda i: nu.print(i)))0
1A tree body under the default name:_ = nu.run(nu.ForRangeDo(0, 2, nu.print(nu.Attr("index"))))0
1Undocumented: yields.
ForeverDo
ForeverDo(body) - runs body on loop forever.
ForeverDo(body)Path nu.core.flows.ForeverDo. Kind Control, sort control, cardinality void. Arity 1 (1 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
body | the loop body, re-run with no condition to stop it. |
Notes
- No parameter slot at all: the only child is a body, so
param_slotskeeps the empty default. - Used for standing ticks (a reactive loop, a periodic
Delay+ action) that end only when the surrounding process is stopped or errors, not from anything inside the atom itself.
Undocumented: yields, example.
IfDo
IfDo(cond, then, else_=None) - runs then or else_ based on cond.
IfDo(cond, then, else_=None)Path nu.core.flows.IfDo. Kind Control, sort control, cardinality void. Arity 3 (2 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
cond | object | the condition to test. | |
then | object | the body to run when cond is truthy. | |
else_ | object | None | the body to run when cond is falsy. Optional: leave it out to run nothing on a falsy cond. |
Notes
condis evaluated exactly once per run, unlikeWhileDowhich re-checks it every turn.- A falsy
condwith noelse_runs nothing at all. An EMPTYcondcounts as false.
Example
_ = nu.run(nu.IfDo(nu.Literal(True), nu.print("yes"), nu.print("no")))yesUndocumented: yields.
SwitchDo
SwitchDo(selector, cases, default=None) - runs the case body keyed by selector's value.
SwitchDo(selector, cases, default=None)Path nu.core.flows.SwitchDo. Kind Control, sort control, cardinality void. Arity 3 (2 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
selector | object | the value to match against the case keys. | |
cases | Mapping[object, Term] | a mapping from match key to case body, checked in order. | |
default | object | None | the body to run when no key matches. Optional: leave it out to run nothing on a miss. |
Notes
- The case keys are intrinsic constants of the switch, not children: they live in
payloadso they survivewith_children, and are paired by position with the case bodies. - Keys are checked in the order
caseswas given, first equal match wins; a later duplicate key is unreachable. selectoris evaluated once per run, before any key comparison. An EMPTY selector matches no key.
Example
_ = nu.run(nu.SwitchDo(nu.Literal("b"), {"a": nu.print(1), "b": nu.print(2)}))2Undocumented: yields.
WhileDo
WhileDo(cond, body) - runs body on loop while cond stays truthy.
WhileDo(cond, body)Path nu.core.flows.WhileDo. Kind Control, sort control, cardinality void. Arity 2 (2 required).
Arguments
| Name | Type | Default | Meaning |
|---|---|---|---|
cond | the condition, checked before each turn. | ||
body | the loop body. |
Notes
condis re-evaluated before every turn, including the first, so a falsycondat the start runsbodyzero times.- An EMPTY
condcounts as false and ends the loop. - No built-in turn cap: a
condthat never turns falsy loops forever.
Example
count = lambda i: nu.WhileDo(i < 3, i.set(i + 1)) >> nu.print(i)
_ = nu.run(nu.let(0, count))3Undocumented: yields.