Nucleoid/docs/nuc-0007.md at main · NucleoidAI/Nucleoid · GitHub
Skip to content

Latest commit

 

History

History
312 lines (224 loc) · 7.63 KB

nuc-0007.md

File metadata and controls

312 lines (224 loc) · 7.63 KB

NUC 7 -- Control Flow

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

Abstract

  • Specifies if, else if, else, and for ... of.
  • A conditional is a standing rule: it re-evaluates when its condition or branches change.
  • A for ... of loop runs once over the values present at the time.

Motivation

  • 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.

Rationale

  • 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 ... of does not re-run. Looping again on every change would create duplicate instances; the loop is an action, and its results are ordinary state afterwards.

Specification

Conditional statements

  • if takes 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
  • n is false.

  • After m = true, n is true.

  • 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.shape is null.
  • After toy1.color = "RED", it is "CIRCLE".

Truthiness

  • A condition need not be a comparison; a bare value is tested for presence.
  • null and an undefined property are false; 0 is false; a non-empty value is true.
  • An instance is true, so a query result can be tested directly.
  • not inverts the test.
class Device(profile: str):
    this.profile = profile

if $Device.profile:
    $Device.active = true
  • A device with a profile has active true; one without has null.
{
    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, and not t.done reads 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.

Branch dependencies

  • 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
  • pH is "ACIDIC".
  • After compound = 0.000000001, pH is "BASIC".
  • After basic = '+7', pH is "+7".

else if and else

  • else if chains further conditions.
  • else is 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.1 and point 1, score is -0.1.
  • After point = 2, score is -0.2.

Re-declaring a conditional

  • A later conditional with the same condition replaces the earlier one.
if p < 1:
    r = p * 10

if p < 1:
    r = s * 10
  • r follows s, not p.

Order of conditionals

  • 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, result is 5.
trigger = 0
enabled = false
result = 0

if enabled and trigger > 0:
    result = 1

if trigger > 0:
    result = 2

enabled = true
trigger = 1
  • result is 2. Updating only enabled did 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.

Nesting

  • 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 gravity 9.8, time 10, distance 480, hit is true.
  • After target = false, hit is false.

for ... of

  • 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 Summary per existing Question.

  • A Question created afterwards does not get a Summary.

  • 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.length is 4.

What for ... of iterates

  • Only values that are instances defined in the state are visited.
  • Plain objects, literals, and objects that merely carry an id are skipped.
entries = []
entries.push("PENDING")
entries.push({ "id": "task9" })

task1 = Task()
entries.push(task1)

for entry of entries:
    visited.push(entry)
  • visited.length is 1 and visited[0] is task1.

Locals in a loop body

  • 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.

  • .value may be used in a loop body to read a counter without depending on it.

Errors

Condition Error
A condition reading a variable that is not defined ReferenceError("<name> is not defined")
  • A throw inside a conditional or loop rolls back the whole transaction, including instances created earlier in the same loop; see NUC 9.

Reference Implementation

Copyright

Copyright 2020 Nucleoid

This document is licensed under the Apache License, Version 2.0. See LICENSE for the full text.