NUC: 7
Title: Control Flow
Author: Can Mingir <canmingir@gmail.com>
Status: Draft
Type: Standards Track
Category: Runtime features
Created: 31-Jul-2026
Requires: 1, 2, 4, 5, 6
- Specifies
if,else if,else, andfor ... of. - A conditional is a standing rule: it re-evaluates when its condition or branches change.
- A
for ... ofloop runs once over the values present at the time.
- Conditions in Nucleoid express policy -- when something counts as full, expired, or approved -- and policy must keep applying as data changes.
- A conditional that ran once would leave derived state stale the moment its input moved.
- Iteration is the exception: creating one instance per element is an act, not a standing relationship.
- A conditional is re-evaluated, not re-run. It depends on its condition and on the expressions in its branches, so changing either updates the result.
- A false condition leaves the target untouched rather than clearing it. This lets several conditionals contribute to the same property without erasing each other.
- Re-declaring a conditional replaces it. The same rule as re-assignment: the last statement received is the one that holds.
for ... ofdoes not re-run. Looping again on every change would create duplicate instances; the loop is an action, and its results are ordinary state afterwards.
iftakes a condition and an indented body.- The body may assign variables, properties, or class-level properties.
- The statement re-evaluates when the condition's dependencies change.
- While the condition is false, the target keeps its previous value.
m = false
n = false
if m == true:
n = m and true
-
nisfalse. -
After
m = true,nistrue. -
A conditional over a property behaves the same way.
class Toy:
pass
toy1 = Toy()
toy1.color = "BLUE"
if toy1.color == "RED":
toy1.shape = "CIRCLE"
toy1.shapeisnull.- After
toy1.color = "RED", it is"CIRCLE".
- A condition need not be a comparison; a bare value is tested for presence.
nulland an undefined property are false;0is false; a non-empty value is true.- An instance is true, so a query result can be tested directly.
notinverts the test.
class Device(profile: str):
this.profile = profile
if $Device.profile:
$Device.active = true
- A device with a profile has
activetrue; one without hasnull.
{
user = User["user0"]
if user:
user.name = "TEST"
}
- The body runs only when the query finds an instance.
if not device:is the same test inverted, andnot t.donereads a property that may be undefined.- The same rule applies inside a lambda:
list.filter(n => n % 2)keeps the elements whose remainder is non-zero.
- The expressions in a branch are dependencies too.
- Changing a value the taken branch reads updates the result without re-running the statement.
compound = 0.0001
acidic = 'ACIDIC'
basic = 'BASIC'
if compound > 0.0000001:
pH = acidic
else:
pH = basic
pHis"ACIDIC".- After
compound = 0.000000001,pHis"BASIC". - After
basic = '+7',pHis"+7".
else ifchains further conditions.elseis the fallback.- Each condition may read a different dependency.
if fraction > 1:
score = fraction * point * 3
else if fraction > 0:
score = fraction * point * 2
else:
score = fraction * point
- With
fraction-0.1andpoint1,scoreis-0.1. - After
point = 2,scoreis-0.2.
- A later conditional with the same condition replaces the earlier one.
if p < 1:
r = p * 10
if p < 1:
r = s * 10
rfollowss, notp.
- Conditionals are applied in the order received.
- When several match, the last one determines the value.
- Re-evaluation does not change that order. Explicitly declaring a conditional again gives it a new priority.
- A conditional waits for its queued upstream dependencies before running.
any = 0
if any > 1:
result = 1
if any > 2:
result = 2
if any > 3:
result = 3
if any > 2:
result = 4
if any > 1:
result = 5
- After
any = 4,resultis5.
trigger = 0
enabled = false
result = 0
if enabled and trigger > 0:
result = 1
if trigger > 0:
result = 2
enabled = true
trigger = 1
resultis2. Updating onlyenableddid not move the first rule after the second.- If another rule changes an input later in the same cascade, the conditional re-evaluates again.
- Writing the same value does not restart feedback. A cascade that keeps changing values instead raises
TypeError("Propagation did not settle")and rolls back.
- Conditionals may be nested inside blocks and inside each other.
- A conditional body may itself introduce locals.
- Nested conditions and blocks contribute their reads to the enclosing rule; they are not filed as independent standing declarations.
{
dist = 1 / 2 * gravity * time * time
if dist > distance:
hit = target
}
- With
gravity9.8,time10,distance480,hitistrue. - After
target = false,hitisfalse.
for <name> of <source>iterates a class list or a list value.- The body runs once per element, at the time the statement is received.
- The loop does not re-run when the source changes later.
- Instances created in the loop are ordinary instances with generated ids.
- Conditions and blocks inside the loop execute as part of its one-shot body. Changing a value they read does not re-run them afterwards.
class Question(rate: int):
this.rate = rate
class Summary(question):
this.question = question
for question of Question:
Summary(question)
-
One
Summaryper existingQuestion. -
A
Questioncreated afterwards does not get aSummary. -
A body may contain conditionals.
for package of Package:
if package.weight > 20:
package.tier = "HEAVY"
else if package.weight > 5:
package.tier = "MEDIUM"
else:
package.tier = "LIGHT"
- Loops may nest, producing the cross product.
for row of Row:
for column of Column:
Cell(row, column)
- With two rows and two columns,
Cell.lengthis4.
- Only values that are instances defined in the state are visited.
- Plain objects, literals, and objects that merely carry an
idare skipped.
entries = []
entries.push("PENDING")
entries.push({ "id": "task9" })
task1 = Task()
entries.push(task1)
for entry of entries:
visited.push(entry)
visited.lengthis1andvisited[0]istask1.
- A loop body may compute locals.
- Those locals do not create standing dependencies for later changes.
VALUE = 10
for item of Item:
i = 10 * VALUE
item.score = i
-
Every item's score is
100. -
After
VALUE = 20, scores are unchanged until the loop is issued again. -
.valuemay be used in a loop body to read a counter without depending on it.
- A
throwinside a conditional or loop rolls back the whole transaction, including instances created earlier in the same loop; see NUC 9.
- None. See NUC 1.
Copyright 2020 Nucleoid
This document is licensed under the Apache License, Version 2.0. See LICENSE for the full text.
