Worked examples of the language, each one a complete program that runs as written.
The sections follow README.md one for one, so an example can
be read next to the behaviour it demonstrates. Every example is taken from
nucleoid.spec.md, which is normative; where this document and the
specification disagree, the specification wins. See NUC 0 for the
index of NUC documents.
The examples are executable. cargo test --test docs runs every block on
this page and checks the assertions in it, so an example that stops being true
fails the build.
- Statements and State
- Variables
- Expressions
- Types and Instances
- Properties
- Class-Level Rules
- Blocks and Scope
- Control Flow
- Functions
- Transactions
- Built-in Objects
- Reasoning Operations
- Error Reference
Syntax Summary is a table of forms rather than a behaviour, so it has no example of its own.
A statement runs against the state, and an expression statement returns its value
- A statement is run against the state and changes it.
- An expression statement is evaluated against what the state already holds.
i = 1
assert(i == 1, true)
j = 1
assert(j + 2, 3)
Statements apply in the order received, and the last one to assign a target holds
- All five conditionals are standing rules and all are kept.
- When
anybecomes 4 the first, second and fourth match; the last of them holds.
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
any = 4
assert(result, 5)
Full detail: README.md §1, NUC 2.
An assignment states a relationship, and stating it again replaces it
cis the sum ofaandb, and stays the sum as they change.- Assigning
cagain discards the dependency onaand records the one onb.
a = 1
b = 2
c = a + b
assert(c, 3)
a = 2
assert(c, 4)
c = b + 3
assert(c, 5)
b = 4
assert(c, 7)
Upstream dependencies settle first even when their declaration is newer
totalreads both the head and the tail of the chain.- Replacing
middledoes not lettotalcombine a new head with an old tail.
source = 1
middle = source + 1
last = middle + 1
total = source + last
middle = source + 2
source = 2
assert(middle, 4)
assert(last, 5)
assert(total, 7)
A variable that reads itself reads only its value
- The occurrence on the right is the current value, not a dependency.
- This is why it is not a cycle.
radius = 10
radius += 10
assert(radius, 20)
Compound assignments keep their reactive relationships
amount += stepstates the same relationship asamount = amount + step.- Changing
stepre-evaluates that relationship againstamount's current value. - Other arithmetic operators use the same lowering;
.valueon the right still takes a snapshot. - These compact forms are specified by accepted NUC 13.
amount = 10
step = 2
amount += step
assert(amount, 12)
step = 3
assert(amount, 15)
The value property reads a variable without depending on it
widthis fixed againstgoldenRatioand still followsaltitude.- It is how a relationship is broken deliberately rather than by accident.
goldenRatio = 1.618
altitude = 10
width = goldenRatio.value * altitude
assert(width, 16.18)
goldenRatio = 1.62
assert(width, 16.18)
altitude = 100
assert(width, 161.8)
Deleting a variable clears what depended on it
deletereports whether anything was removed.- The statements that read the name are gone, and using it again is a reference error.
t = 1
q = t + 1
assert(q, 2)
assert(delete q, true)
t = 2
try:
q
catch error:
assert(error, ReferenceError("q is not defined"))
Full detail: README.md §2, NUC 2.
A string may be written three ways, and a template interpolates what it reads
- Single quotes, double quotes and backticks all give a string.
${...}interpolates, and what it reads is a dependency like any other.
a = 123
assert('New String', "New String")
assert("New String", "New String")
assert(`New String`, "New String")
assert(`New ${a} String`, "New 123 String")
Logical operators have a word spelling and a symbol spelling
and,orandnotare the same operators as&&,||and!.
condition = false
assert(condition or true, true)
assert(condition || true, true)
assert(not condition and true, true)
assert(!condition && true, true)
A list indexes with brackets
states = ["NY", "GA", "CT", "MI"]
assert(states[2], "CT")
Length is a dependency like any other
i1follows the length ofstr1, and so does the condition below it.
str1 = "ABC"
i1 = str1.length + 1
assert(i1, 4)
str1 = "ABCD"
assert(i1, 5)
if str1.length > 5:
i2 = i1
str1 = "ABCDEF"
assert(i2, 7)
Reusable readings keep named fields concise
- This isolated Draft NUC 17 candidate returns an object whose ordinary identifier fields reuse their names.
- The function still reads its parameters; changing the argument updates the existing reactive call relationship.
- Named arrows may return
{ reading, unit }directly; anonymous arrows returning an object still needreading => ({ reading }).
reading = 7
unit = "C"
def pack reading unit =>
{ reading, unit }
report = pack(reading, unit)
assert(report.reading, 7)
assert(report.unit, "C")
reading = 9
assert(report.reading, 9)
Full detail: README.md §3, NUC 10.
The isolated NUC 47 revision supports positional, named and positional-then-named construction with ordinary headers and no star. See its paired reference example. The positional programs below remain unchanged; explicit grouped assignments are the escape for caller-write arguments rather than bare constructor labels.
Empty types need no placeholder body
- The header declares the same empty type as an indented pass-only body.
- Instance registration and class-level rules keep their existing behavior.
class Entity
entity1 = Entity()
$Entity.enabled = true
assert(typeof Entity, List)
assert(entity1.enabled, true)
assert(Entity.length, 1)
A type declares its constructor, and instances register themselves
Shapeis the list of instances;$Shapeis the type.- The variable name becomes the instance
id, so the instance is addressable by it.
class Shape(type: str):
this.type = type
shape1 = Shape("Square")
assert(shape1, { "id": "shape1", "type": "Square" })
assert(Shape["shape1"], { "id": "shape1", "type": "Square" })
assert(Shape.length, 1)
this and self refer to the same instance
- Either keyword can be used in constructors, methods, and class-level rules.
class Counter(count: int):
self.count = count
def same_receiver() => this == self
def set_this(count: int):
this.count = count
return self.count
def set_self(count: int):
self.count = count
return this.count
$Counter.doubled = this.count + self.count
counter = Counter(2)
assert(counter.same_receiver(), true)
assert(counter.doubled, 4)
assert(counter.set_self(3), 3)
assert(counter.doubled, 6)
assert(counter.set_this(5), 5)
assert(counter.doubled, 10)
A subtype calls its supertype's constructor
- The supertype is named after the colon, and
super(...)invokes it.
class Person(name: str):
this.name = name
class Student: Person
def init name school:
super(name)
this.name = name
this.school = school
student1 = Student("Emma", "Riverside High")
assert(student1, { "id": "student1", "name": "Emma", "school": "Riverside High" })
Bare super preserves inherited reactive properties
superruns the zero-argument parent constructor on the existing child.- Inherited class-level properties still update when their source changes.
seed = 4
class Parent:
def init():
this.ready = true
$Parent.amount = seed
class Child: Parent
def init():
super
child = Child()
assert(child.ready, true)
assert(child.amount, 4)
seed = 6
assert(child.amount, 6)
Re-declaring a type keeps its instances and its rules
- The instances made before the new declaration are still there, and the rule still holds for them.
- An attribute the earlier instances never had is
nullon them.
class Message
$Message.read = false
message1 = Message()
class Message(payload: str):
this.payload = payload
assert(message1.read, false)
assert(message1.payload, null)
message2 = Message("MESSAGE")
assert(message2.read, false)
assert(message2.payload, "MESSAGE")
Full detail: README.md §4, NUC 3; candidate bare calls: NUC 16.
A property may be assigned from one that does not exist yet
- Reading something undefined yields
nulland re-evaluates when it arrives.
class Order
order1 = Order()
order1.upc = "04061" + order1.barcode
assert(order1.upc, null)
order1.barcode = "94067"
assert(order1.upc, "0406194067")
A property reads through a reference, and the dependency follows it
server1.summaryreadsip1throughserver1.ip, so a change toip1reaches it.
class Server
class IP
server1 = Server()
server1.name = "HOST1"
ip1 = IP()
ip1.address = "10.0.0.1"
server1.ip = ip1
server1.summary = server1.name + "@" + server1.ip.address
assert(server1.summary, "HOST1@10.0.0.1")
ip1.address = "10.0.0.2"
assert(server1.summary, "HOST1@10.0.0.2")
Deleting a property makes its dependents null
- The properties the deleted one did not feed are left untouched.
class Agent
agent = Agent()
agent.time = 52926163455
agent.location = "CITY"
agent.report = agent.time + "@" + agent.location
assert(agent.report, "52926163455@CITY")
delete agent.time
assert(agent.report, null)
assert(agent.location, "CITY")
Full detail: README.md §5, NUC 4.
A rule on a type holds for every instance of it
- The rule reaches instances that already exist, not only later ones.
- Stating the rule again replaces it, and the instances are brought up to date.
class Employee
employee1 = Employee()
employee1.id = 1
$Employee.username = "E" + $Employee.id
assert(employee1.username, "E1")
$Employee.username = "F" + $Employee.id
assert(employee1.username, "F1")
employee1.id = 2
assert(employee1.username, "F2")
A conditional rule leaves the instances it does not match untouched
- Each instance is evaluated on its own.
- A bare property as the condition is tested for presence, so a device without a profile does not match.
class Device(profile: str):
this.profile = profile
if $Device.profile:
$Device.active = true
device1 = Device()
device2 = Device("PROFILE-1")
assert(device1.active, null)
assert(device2.active, true)
A branch chain over a type follows its branches as well as its condition
statusdepends on the capacity that chooses the branch and on the value the branch assigns.
class Storage
normal = "NORMAL"; low = "LOW"; empty = "EMPTY"
if $Storage.capacity > 25:
$Storage.status = normal
else if $Storage.capacity > 0:
$Storage.status = low
else
$Storage.status = empty
storage1 = Storage()
storage1.capacity = 23
assert(storage1.status, "LOW")
low = "L"
assert(storage1.status, "L")
A rule may read across types and is checked as it is declared
- The rule aggregates the
Registrationlist for eachUser. - Both registrations already point at
user1, so declaring it throws.
class User
class Registration
user1 = User()
registration1 = Registration()
registration1.user = user1
registration2 = Registration()
registration2.user = user1
try:
if Registration.filter(r => r.user == $User).length > 1:
throw "USER_ALREADY_REGISTERED"
catch error:
assert(error, "USER_ALREADY_REGISTERED")
Full detail: README.md §6, NUC 5.
A block's locals do not survive it, but the dependencies carried through them do
valueis local to the block and gone after it.jwas stated in terms ofvalue, so it still followsh.
h = 1
{
value = h * 2
j = value * 2
}
assert(j, 4)
h = 2
assert(j, 8)
A local shadows an outer name, and the lowest binding wins
- The
piinside the block is a different name from the one outside it.
pi = 3.14
number = pi
{
pi = 3.141
number = pi
}
assert(number, 3.141)
A block may guard its work and return a local
- The query finds nothing, so the guard throws and the block is rolled back.
class Device(code: str):
this.code = code
device1 = Device("A0")
device2 = Device("B1")
try:
{
device = Device.find(d => d.code == "A1")
if not device:
throw "INVALID_DEVICE"
return device
}
catch error:
assert(error, "INVALID_DEVICE")
Full detail: README.md §7, NUC 6.
A conditional is a standing rule, not a branch taken once
- The condition is false at first, which leaves
shapealone rather than clearing it. - The rule is re-evaluated when the colour it reads changes.
class Toy
toy1 = Toy()
toy1.color = "BLUE"
if toy1.color == "RED":
toy1.shape = "CIRCLE"
assert(toy1.shape, null)
toy1.color = "RED"
assert(toy1.shape, "CIRCLE")
Re-declaring a conditional replaces it
- The second conditional takes the place of the first, so
rfollowssfrom then on.
p = 0.01
s = 0.02
if p < 1:
r = p * 10
if p < 1:
r = s * 10
assert(r, 0.2)
s = 0.03
assert(r, 0.3)
Re-evaluating a conditional preserves its declaration priority
- Updating
enabledruns only the first rule, but does not make it the newest. - When
triggerchanges, the second matching rule still wins.
trigger = 0
enabled = false
result = 0
if enabled and trigger > 0:
result = 1
if trigger > 0:
result = 2
enabled = true
trigger = 1
assert(result, 2)
A rule runs again when another input changes later in the same cascade
- Neither conditional is discarded after its first turn.
- Derived values settle with the final inputs.
first = 0
second = 0
total = first + second
if first > 0 and first < 4:
second = first + 1
if second > 0 and second < 4:
first = second + 1
first = 1
assert(first, 3)
assert(second, 4)
assert(total, 7)
A conditional inside a loop is not a standing rule
- The first loop sees
limitat zero and leaves the instance untouched. - Changing
limitdoes not run that body again; another explicit loop does.
class Item
item = Item()
item.amount = 0
limit = 0
for current of Item:
if limit > 0:
current.amount = limit
limit = 2
assert(item.amount, 0)
for current of Item:
if limit > 0:
current.amount = limit
assert(item.amount, 2)
limit = 3
assert(item.amount, 2)
A for of statement runs its body once, over what is there at the time
- The loop is carried out immediately; it does not re-run when
Questiongrows. - What it creates is declarative all the same, so the rule on
Summarystill reaches the instances it made.
class Question
question1 = Question()
question2 = Question()
question2.archived = true
question3 = Question()
class Summary(question):
this.question = question
$Summary.type = "DAILY"
for question of Question:
if not question.archived:
Summary(question)
assert(Summary.length, 2)
assert(Summary[0].question.id, "question1")
assert(Summary[1].question.id, "question3")
$Summary.type = "WEEKLY"
assert(Summary[0].type, "WEEKLY")
assert(Summary[1].type, "WEEKLY")
Full detail: README.md §8, NUC 7.
A function call in an assignment depends on the function and its arguments
def generate number => number * 10
random = 10
number = generate(random)
assert(number, 100)
random = 20
assert(number, 200)
Single parameter named arrows preserve live inputs
fee = 2
def total amount => amount + fee
invoice = total(5)
assert(invoice, 7)
fee = 3
assert(invoice, 8)
NUC 18 specifies parameter-parenthesis omission for a named
arrow definition or method with exactly one untyped ordinary identifier
parameter. def total(amount) => amount + fee remains valid; the optional
untyped multiple-parameter arrow spelling is demonstrated in
bare multiple-parameter named arrows.
Calls retain their parentheses; typed/mixed multiple-parameter arrows have the
separate NUC29 spelling below. The expression still follows
NUC 12. NUC 19
specifies the corresponding narrow indented-colon form, demonstrated in
single-parameter indented block definitions.
Named callbacks and aliases follow redefinition; value freezes the definition
def scale number => number * 2
callback = scale
snapshot = scale.value
result = callback(3)
frozen = snapshot(3)
mapped = [1, 2].map(scale)
def scale number => number * 3
assert(result, 9)
assert(frozen, 6)
assert(mapped, [3, 6])
scale = number => number * 4
assert(result, 12)
assert(frozen, 6)
assert(mapped, [4, 8])
def scale number => number * 5
assert(result, 15)
assert(frozen, 6)
assert(mapped, [5, 10])
Callable properties follow replacement and deletion through either spelling
class Handler
handler = Handler()
handler.operation = number => number + 1
dotted = handler.operation(2)
indexed = handler["operation"](2)
handler.operation = null
assert(dotted, null)
assert(indexed, null)
handler.operation = number => number + 2
assert(dotted, 4)
assert(indexed, 4)
delete handler.operation
assert(dotted, null)
assert(indexed, null)
handler.operation = number => number + 3
assert(dotted, 5)
assert(indexed, 5)
A null or non-callable parameter shadows an outer function
def operation number => 99
def apply operation number => operation(number)
callback = null
result = apply(callback, 3)
assert(result, null)
callback = number => number * 2
assert(result, 6)
failure = null
try:
apply(7, 3)
catch error:
failure = error
assert(failure, TypeError("operation is not a function"))
assert(operation(3), 99)
A lambda has three equivalent forms
list = [1, 2, 3]
assert(list.find(function(element) { return element == 3 }), 3)
assert(list.find(element => { return element == 2 }), 2)
assert(list.find(element => element == 1), 1)
A query over a class list depends on the population and on what it reads
- The chain is re-run when either threshold changes, not when it is written.
class Result(score: int):
this.score = score
Result(10); Result(15); Result(20)
upperThreshold = 18
lowerThreshold = 12
list = Result.filter(r => r.score > lowerThreshold).filter(r => r.score < upperThreshold)
assert(list.length, 1)
assert(list[0].score, 15)
lowerThreshold = 7
assert(list.length, 2)
assert(list[0].score, 10)
assert(list[1].score, 15)
upperThreshold = 14
assert(list.length, 1)
assert(list[0].score, 10)
Full detail: README.md §9, NUC 8.
A statement and everything it triggers are one transaction
- The rule throws while
a = 6is propagating, so the assignment is rolled back. - Afterwards the state is exactly what it was.
a = 5
if a > 5:
throw 'INVALID_VALUE'
try:
a = 6
catch error:
assert(error, "INVALID_VALUE")
assert(a, 5)
A caught failure cancels only the propagation it queued
- The observer of
sourcemust not run for the aborted update. - The unrelated dependent of
triggerwas already queued and still runs.
trigger = 0
source = 1
observations = 0
if source > 0:
observations += 1
if trigger > 0:
try:
source = 2
throw "ABORT"
catch error:
if error != "ABORT":
throw error
remaining = trigger + 10
trigger = 1
assert(source, 1)
assert(observations, 1)
assert(remaining, 11)
A deletion permits temporary undefined reads only during its cascade
- Dependents settle to
nullwhile the removed name propagates. - The direct read after the cascade is a reference error, not another undefined read.
trigger = 0
source = 1
dependent = source + 1
if trigger > 0:
delete source
def read_after_update():
trigger = 1
try:
source
catch error:
return error
return "NO_ERROR"
assert(read_after_update(), ReferenceError("source is not defined"))
assert(dependent, null)
An instance rejected at creation is not registered and its name is not bound
- The rule throws while the instance is being made, so nothing is left behind.
class User(first: str, last: str):
this.first = first
this.last = last
if $User.first.length < 3:
throw 'INVALID_USER'
try:
user1 = User('F', 'L')
catch error:
assert(error, "INVALID_USER")
assert(User.length, 0)
try:
user1
catch error:
assert(error, ReferenceError("user1 is not defined"))
A thrown value may be a number or a string
- What
catchbinds is the value that was thrown.
length = 0.1
try:
if length < 1:
throw length
catch error:
assert(error, 0.1)
try:
if length < 1.1:
throw 'length'
catch error:
assert(error, "length")
Full detail: README.md §10, NUC 9.
A list function re-evaluates when the list it reads is mutated
countis stated once and answers for every laterpushandpop.
list = []
count = list.filter(n => n % 2)
list.push(1)
assert(count.length, 1)
list.push(2)
assert(count.length, 1)
list.push(3)
assert(count.length, 2)
list.pop()
assert(count.length, 1)
Built-in calls take part in the graph like any other expression
String.fromCharCodeandMath.floorare read by a rule, which then holds for the instance that already exists.
class Product
product1 = Product()
class Quality
product1.quality = Quality()
product1.quality.score = 15
assert(product1.quality.class, null)
$Quality.class = String.fromCharCode(65 + Math.floor($Quality.score / 10))
assert(product1.quality.class, "B")
A regular expression may guard a type
- The guard is a standing rule, so it is the assignment that fails, not a later check.
class User
if not /.{4,8}/.test($User.password):
throw 'INVALID_PASSWORD'
user1 = User()
assert(user1.password, null)
try:
user1.password = 'PAS'
catch error:
assert(error, "INVALID_PASSWORD")
Full detail: README.md §11, NUC 10.
A live explanation follows rewritten ancestry even when its value stays the same
- Adding
dto the relationship changes the explanation before it changesc. - Later changes to
dupdate the explanation's values too.
a = 1
b = a + 2
c = b * 2
trace = why c
d = 0
b = a + d + 2
assert(trace.length, 4)
assert(trace[0].holds, 6)
assert(trace[1].from, ["a", "d"])
d = 5
assert(trace[0].holds, 16)
An empty impact selection follows new relationships and their removal
- The source does not have to change for its impact to change.
a = 1
exposure = affects a
assert(exposure, [])
b = a + 1
c = b * 2
assert(exposure.map(step => step.node), ["b", "c"])
b = 0
assert(exposure, [])
b = a + 1
assert(exposure.map(step => step.node), ["b", "c"])
An empty model selection follows declarations and deletions but excludes observers
inventory = model |> why
assert(inventory, [])
a = 1
b = a + 1
assert(inventory.map(step => step.node), ["a", "b"])
delete b
assert(inventory.map(step => step.node), ["a"])
b = a + 2
trace = why b
assert(inventory.map(step => step.node), ["a", "b"])
assert(inventory[1].holds, 3)
trace = a + 0
assert(inventory.map(step => step.node), ["a", "b", "trace"])
assert((affects a).map(step => step.node), ["b", "trace"])
trace = why b
assert(inventory.map(step => step.node), ["a", "b"])
assert((affects a).map(step => step.node), ["b"])
An explanation stays subscribed while its property is absent
class Item
item = Item()
trace = why item.amount
assert(trace, [])
item.amount = 3
assert(trace[0].holds, 3)
assert(trace[0].node, "item.amount")
delete item.amount
assert(trace, [])
item.amount = 4
assert(trace[0].holds, 4)
An impact walk stops at reasoning observers
- A value computed from an explanation is not an indirect model consequence of the explained source.
a = 1
b = a + 1
trace = why b
size = trace.length
exposure = affects a
assert(exposure.map(step => step.node), ["b"])
secondTrace = why b
secondSize = secondTrace.length
assert(exposure.map(step => step.node), ["b"])
The value property freezes a selection against structural changes
a = 1
exposure = affects a
frozen = exposure.value
b = a + 1
assert(exposure.map(step => step.node), ["b"])
assert(frozen, [])
a = 2
assert(exposure[0].holds, 3)
assert(frozen, [])
A rejected rewrite restores the explanation and its original subscriptions
a = 1
alternative = 1
b = a + 1
trace = why b
exposure = affects a
try:
b = alternative + 1
throw "REJECTED_REWRITE"
catch error:
assert(error, "REJECTED_REWRITE")
assert(trace[0].from, ["a"])
assert(exposure.map(step => step.node), ["b"])
alternative = 4
assert(trace[0].holds, 2)
a = 3
assert(trace[0].holds, 4)
b = alternative + 1
assert(trace[0].from, ["alternative"])
assert(exposure, [])
Full detail: README.md §12, NUC 11.
Reading something that was never defined is a reference error
- The message names what was missing, and is part of the observable behaviour.
try:
t = e + 1
catch error:
assert(error, ReferenceError("e is not defined"))
An assignment that would close a cycle is refused
number2already readsnumber1, sonumber1may not be stated in terms ofnumber2.
number1 = 10
number2 = number1 * 10
assert(number2, 100)
try:
number1 = number2 * 10
catch error:
assert(error, TypeError("Circular Dependency"))
value may not be used as a property name
- The name is reserved for reading a value without depending on it.
class Schedule
class Place
value = Schedule()
assert(value, { "id": "value" })
try:
value.value = Place()
catch error:
assert(error, TypeError("Cannot use 'value' as a property"))
A class-level rule may not be declared in a non-class block
class Person
person1 = Person()
person1.weight = 90
person1.height = 1.8
try:
{
weight = person1.weight
height = person1.height
$Person.bmi = weight / (height * height)
}
catch error:
assert(error, SyntaxError("Cannot define class declaration in non-class block"))
Full detail: README.md §13, NUC 9.
Single parameter block definitions preserve live inputs
fee = 2
def total amount:
if amount > 0:
return amount + fee
return fee
invoice = total(5)
assert(invoice, 7)
assert(total(0), 2)
fee = 3
assert(invoice, 8)
assert(total(0), 3)
NUC 19 specifies this optional spelling for one untyped ordinary
identifier and a real indented colon suite. The same body under
def total(amount): remains valid, including both return paths and live
external inputs. Calls and all other accepted signatures retain their syntax
and behavior; inline and brace suites do not gain the bare parameter spelling.
Typed single parameter named arrows preserve live inputs
fee = 2
def add_fee amount: int => amount + fee
result = add_fee(5)
assert(result, 7)
fee = 4
assert(result, 9)
def add_fee amount: int => amount * fee
assert(result, 20)
NUC 20 specifies this optional spelling, keeping the existing
def add_fee(amount: int) => ... form valid and equivalent. Only immediate
Identifier, Colon, Identifier, Arrow tokens after the required name gain
the optional spelling; the annotation is unchanged metadata. Typed bare inline/braced blocks,
other parameter/type forms and logical header breaks remain unsupported.
NUC18/19 spellings, expression bodies, calls and runtime behavior are unchanged.
Numeric clamping bounds live inventory levels
stock = 12
minimum = 0
capacity = 10
usable = Math.clamp(stock, minimum, capacity)
assert(usable, 10)
stock = -3
assert(usable, 0)
minimum = 2
assert(usable, 2)
stock = 7
assert(usable, 7)
capacity = 5
assert(usable, 5)
NUC 21 specifies this static runtime capability; its proposal
header records its status. The result follows ordered min/max coercion,
including numeric NaN, equal/reversed bounds, infinities and signed zeros.
Omitted inputs supply numeric NaN; extra argument expressions are still
evaluated left to right and included in the existing undefined gate.
See README.md §11 for the reference example
and static/member-read limits.
Numeric interpolation keeps an animated position live
origin = 0
destination = 100
progress = 0.25
coordinate = Math.lerp(origin, destination, progress)
assert(coordinate, 25)
progress = 0.5
assert(coordinate, 50)
destination = 80
assert(coordinate, 40)
origin = 20
assert(coordinate, 50)
progress = 1.5
assert(coordinate, 110)
NUC 22 specifies this static runtime capability; its header
records its status. Intrinsic numeric conversion precedes ordinary subtraction,
multiplication and addition in that order. Weights are not clamped, so this
position extrapolates past the destination. Equal/reversed endpoints, infinities,
overflow, cancellation, numeric NaN and signed zeros follow the ordered formula,
not endpoint shortcuts or a fused alternative. Every supplied expression,
including extras, runs once left to right and keeps the existing undefined gate
and live-null/deferred context. See README.md §11
for the reference example, arity and static/member-read limits.
Unit-interval saturation bounds live animation opacity
# Unit-interval saturation bounds live animation opacity
class Fade(level):
this.level = level
$Fade.opacity = Math.saturate($Fade.level)
fade = Fade(0.4)
assert(fade.opacity, 0.4)
fade.level = 2
assert(fade.opacity, 1)
fade.level = -1
assert(fade.opacity, 0)
- NUC 23 specifies static unit-interval saturation.
- The normative saturation section
defines ordered
Math.clamp(value, 0, 1)through the accepted extremum folds. - Intrinsic conversion, numeric
NaN, infinities and signed zeros follow that composition, not input validation, conversion skips or endpoint shortcuts. - Every expression, including ignored extras, runs once left to right with existing dependencies, undefined/live-null gates and transaction recovery.
- Class-rule property syntax and ordinary lookup are unchanged.
- See README.md §11 for the exact executable reference example.
Typed block definitions preserve both live return paths
# Typed block definitions preserve both live return paths
fee = 2
def total amount: int:
if amount > 0:
return amount + fee
return fee
invoice = total(5)
assert(invoice, 7)
assert(total(0), 2)
fee = 3
assert(invoice, 8)
assert(total(0), 3)
- NUC 24 describes the optional single typed parameter spelling.
def total(amount: int):with the same suite remains valid and equivalent; both return paths are used, and external inputs remain live.- Require immediate parameter/colon/annotation/colon tokens followed by normal body newline layout and a real indent. Blank/comment lines are allowed; header-line logical breaks/semicolons, inline/braced bare suites and other parameter/type forms are not added.
- Annotations remain identifier metadata, not type constraints or dependencies.
- Calls, NUC18/19/20 forms, all explicit signatures, no implicit block return, receivers/init/super, scope limitations, snapshots, rollback and resource bounds retain their existing behavior.
- See README.md §9 for the exact executable reference example and the normative typed-block section.
Logical compound assignments keep their reactive relationships
available = false
fallback = false
available ||= fallback
assert(available, false)
fallback = true
assert(available, true)
allowed = true
allowed &&= available
assert(allowed, true)
available = false
assert(allowed, false)
- Accepted NUC 26 adds optional spellings of the existing expanded logical assignments; every paired case passed independent evaluation.
available ||= fallbackmeansavailable = available || (fallback), andallowed &&= availablemeansallowed = allowed && (available).- A standing relationship is always installed, even when its logical evaluation skips the right operand. Later dependency updates use the target's current value, with no new conditional-write or imperative-update mode.
- Old
=,&&,||,and,or, and arithmetic compounds remain valid. - The executable reference counterpart is
Nucleoid preserves logical compound assignment relationshipsintests/reference.md; see README.md §2.
Multiple parameter named arrows keep live inputs
bias = 1
def total left right => left + right + bias
result = total(2, 3)
assert(result, 6)
bias = 4
assert(result, 9)
Under accepted NUC 27,
def total(left, right) => left + right + bias remains valid and exactly
equivalent. Only the complete comma-separated list of at least two untyped
ordinary identifiers immediately followed by => on the same logical line
qualifies. No logical newline, semicolon or comment-induced break is skipped.
Bare inline/braced blocks, defaults, destructuring, rest
parameters, trailing bare commas and return annotations are not added.
Typed/mixed arrow lists have the separate NUC29 spelling below.
Calls and anonymous arrows are unchanged; parenthesized, single-parameter
typed-arrow and typed/untyped single-parameter block alternatives remain valid.
Multiple parameter blocks keep live inputs
bias = 1
def total left right =>
left + right + bias
result = total(2, 3)
assert(result, 6)
bias = 4
assert(result, 9)
- NUC 28 spells the same block as
def total(left, right):with exactly the same body; methods andinitshare the same parser. Two or more untyped ordinary identifiers form the complete comma-separated header immediately followed by its suite colon. - Skip no logical newline, semicolon or comment-induced break inside the header.
The colon must immediately lead to
NewlineorIndent, and the first non-newline/non-semicolon token from that body break must be a realIndent. Blank/comment lines and normal suite layout remain valid;:;does not qualify. - NUC30 separately adds typed/mixed multiple indented block headers below; NUC28's untyped forms remain unchanged. Inline/braced bare suites, trailing bare commas, defaults, destructuring, rest and return annotations remain unsupported. Ordinary indented bodies may contain existing branches and inner blocks.
- All parenthesized forms, NUC27 arrows and single typed/untyped guards remain valid. Binding, explicit/bare/no returns, noncapturing scope, receivers, snapshots, redefinition, graphs and rollback retain the existing block behavior.
Typed multiple parameter named arrows keep live inputs
bias = 1
def total left: int, right => left + right + bias
result = total(2, 3)
assert(result, 6)
bias = 4
assert(result, 9)
- NUC 29 adds an optional spelling of
def total(left: int, right) => left + right + bias, without replacing any accepted form. - Two or more ordinary identifier parameters may mix the existing ordinary identifier annotations in any position; at least one annotation distinguishes the new forms from NUC27's entirely untyped lists. Metadata and argument binding are unchanged, with no runtime type enforcement.
- Require the complete immediate comma-separated sequence followed by
=>; skip no logical newline, semicolon, indentation or comment-induced break. NUC29 itself adds no blocks; NUC30's separate typed/mixed indented spelling follows below. Bare inline/braced suites, trailing bare commas, new parameter/type syntax and call-site omission are not added. - The named return block, body expressions, methods/init/receivers, external dependencies, aliases, snapshots, redefinition and transactions retain their parenthesized behavior. NUC28 untyped blocks and every single guard remain unchanged; blocks gain no implicit return.
- Full grammar, exact errors and positions are in the authoritative typed multiple-parameter arrow section.
Typed multiple parameter blocks keep live inputs
bias = 1
def total left: int, right =>
left + right + bias
result = total(2, 3)
assert(result, 6)
bias = 4
assert(result, 9)
- NUC 30 describes the optional spelling of
def total(left: int, right):with exactly the same indented body, including methods andinit. - The complete immediate list has at least two ordinary identifiers and may
annotate any position with the existing
Colon Identifiermetadata. At least one annotation distinguishes the new forms; all-untyped lists remain NUC28. Annotations do not introduce lookups, dependencies or runtime type checks. - Skip no logical header breaks. Immediately after the suite colon require
NewlineorIndent, then a realIndentafter only normal newline/semicolon layout;:;never qualifies. Blank/comment lines remain suite layout. - A final untyped parameter's following colon is the suite delimiter. A final typed parameter has its annotation colon/name and a separate suite colon. The unchanged suite parser owns that final colon and the entire body.
- Keep every accepted parenthesized/single/multiple form, NUC24 typed blocks and NUC29 arrows. Inline/braced bare suites, trailing commas, richer parameter/type syntax, return annotations and call-site changes are not added.
- Multiple statements, branches, nesting, explicit/bare/no returns, local shadowing, noncapturing scope, receivers/init/super, aliases/snapshots, reactive inputs, graph dependencies and rollback retain the parenthesized behavior.
- Full grammar and exact diagnostics are in the authoritative typed multiple-parameter block section.
Implicit iterator loops keep one shot updates
class Reading
first = Reading()
first.amount = 2
second = Reading()
second.amount = 5
scale = 2
for Reading:
it.scaled = it.amount * scale
assert(first.scaled, 4)
scale = 3
assert(first.scaled, 4)
for Reading:
it.scaled = it.amount * scale
assert(first.scaled, 6)
assert(second.scaled, 15)
- Accepted NUC 31 specifies
for Reading:as exactlyfor it of Reading:with that same genuinely indented body. Existing explicitof/inloops and their arbitrary expressions remain valid. - The source is exactly one ordinary identifier and is evaluated before local
itis bound. Each loop walks its current snapshot once; later changes toscaleor the class population do not re-run a previous body. - Lists likewise visit only registered typed instances, preserving order and duplicates while skipping primitives, null elements and plain objects. Noniterable sources retain their existing errors, without auto-invocation.
- The NUC 51 candidate also accepts String snapshots without changing List filtering; see string iteration.
- Require the immediate colon followed by newline/indent and a real indent
after only normal newline/semicolon layout;
:;never qualifies. No inline/braced/missing-indent, parenthesized or expression-source shorthand is added. - Nested
itshadowing/restoration, receivers, return propagation, body control flow, graph dependencies, rollback and resource limits are unchanged. - Full grammar and exact diagnostics are in the authoritative implicit iterator loop section.
Indented named arrows keep live inputs
- The optional NUC32 layout returns exactly one expression through the existing
named return block; the same colon suite with explicit
returnremains valid. - External inputs remain live; parameter annotations are unchanged metadata.
bias = 1
def total left: int, right =>
left + right + bias
result = total(2, 3)
assert(result, 6)
bias = 4
assert(result, 9)
Smooth cubic transitions preserve clamped endpoints
lower = 0
upper = 8
value = -2
unit = Math.saturate((value - lower) / (upper - lower))
eased = Math.smoothstep(lower, upper, value)
assert(unit, 0)
assert(eased, 0)
value = 2
assert(unit, 0.25)
assert(eased, 0.15625)
value = 6
assert(unit, 0.75)
assert(eased, 0.84375)
value = 10
assert(unit, 1)
assert(eased, 1)
- Accepted NUC 33 defines lower/upper/value argument order, independent live normalization controls and the exact ordered cubic.
- Equal or reversed bounds and nonfinite values follow accepted normalization and maximum-then-minimum composition, without range validation or shortcuts.
Indented else suites preserve conditional chains
class Stock
item = Stock()
item.quantity = 0
limit = 5
if item.quantity > limit:
item.status = "HIGH"
else if item.quantity > 0:
item.status = "LOW"
else
item.status = "EMPTY"
assert(item.status, "EMPTY")
item.quantity = 2
assert(item.status, "LOW")
item.quantity = 8
assert(item.status, "HIGH")
limit = 10
assert(item.status, "LOW")
item.quantity = 0
assert(item.status, "EMPTY")
- Accepted NUC 34 makes only a final real-indented
elsecolon optional; the same source withelse:remains equivalent. - This legacy control deliberately retains every condition colon. NUC 49 also permits their omission before genuine indentation, with unchanged branch order.
Smooth quintic transitions preserve clamped endpoints
lower = 0
upper = 8
value = -2
unit = Math.saturate((value - lower) / (upper - lower))
eased = Math.smootherstep(lower, upper, value)
assert(unit, 0)
assert(eased, 0)
value = 2
assert(unit, 0.25)
assert(eased, 0.103515625)
value = 6
assert(unit, 0.75)
assert(eased, 0.896484375)
value = 10
assert(unit, 1)
assert(eased, 1)
- Accepted NUC 35 specifies lower/upper/value normalization and the ordered quintic, retaining every independent unit-control assertion.
- The clamped normalization supplies the displayed endpoint results; rounded
interior polynomial values are not promised to stay in
[0, 1]. - Equal/reversed bounds, nonfinite values, coercion and deferred reads follow the existing arithmetic composition without validation or shortcuts.
Floor fractional parts preserve negative and rounded endpoints
value = -2.75
whole = Math.floor(value)
fraction = Math.fract(value)
assert(whole, -3)
assert(fraction, 0.25)
value = 0.5
assert(whole, 0)
assert(fraction, 0.5)
value = 2
assert(whole, 2)
assert(fraction, 0)
value = -0.00000000000000000001
assert(whole, -1)
assert(fraction, 1)
- Accepted NUC 36 adds only static
Math.fract(value); the existingvalue - Math.floor(value)remains valid and equivalent. - Convert intrinsically, then compute floor and subtraction in that order.
The tiny negative endpoint rounds to exactly one without clamping; no strict
[0, 1)guarantee is made. Independent floor controls remain unchanged. - Nonfinite values, coercion, omitted inputs, ignored-extra reads/effects, deferred properties, transactions and recovery retain the existing machinery.
Comma-free named arrows keep live inputs
bias = 1
def total left right => left + right + bias
result = total(2, 3)
assert(result, 6)
bias = 4
assert(result, 9)
- NUC 37 adds an additional untyped named-arrow
header guard; the same program with
def total left, right =>remains equivalent. Calls, anonymous arrows, body layout and live dependencies retain their existing grammar and behavior.
Inverse interpolation handles descending live ranges
start = 10
end = 2
sample = 8
weight = Math.inverseLerp(start, end, sample)
assert(weight, 0.25)
sample = 4
assert(weight, 0.75)
sample = 14
assert(weight, -0.5)
start = 18
assert(weight, 0.25)
- Accepted NUC 38 adds only unshadowed static
Math.inverseLerp(start, end, value). The accepted arithmetic(value - start) / (end - start)remains available and equivalent. - Convert intrinsically, then compute numerator subtraction, span subtraction and division in exactly that order. Do not clamp, reorder ranges, shortcut endpoints or scale to avoid overflow. Descending ranges are valid.
- Nonfinite results, missing inputs, ignored-extra effects/reads, deferred properties and transactional recovery retain the existing machinery.
- The authoritative inverse-interpolation section and the reference give the full contract.
Range remapping handles descending live source and target ranges
start = 10
end = 2
targetStart = 30
targetEnd = 10
sample = 8
mapped = Math.remap(start, end, targetStart, targetEnd, sample)
assert(mapped, 25)
sample = 4
assert(mapped, 15)
sample = 14
assert(mapped, 40)
start = 18
assert(mapped, 25)
- Accepted NUC 39 adds only unshadowed static
Math.remap(start, end, targetStart, targetEnd, value). The acceptedMath.lerp(targetStart, targetEnd, Math.inverseLerp(start, end, value))remains available for the same numeric composition. - Convert intrinsically, then subtract, subtract, divide, subtract, multiply and add, with separate rounding in exactly that order. Do not clamp, reorder ranges, rescale or shortcut endpoints; descending ranges and extrapolation are intentional.
- New-call expressions run once in listed order, unlike the nested spelling's target-first argument order. The sealed demonstrations use pure reads.
- Deferred inputs, ignored-extra reads/effects, strict failures and rollback retain the existing machinery. The authoritative remapping section and reference define the full contract.
Comma-free blocks keep live input relationships
bias = 1
def combined left right:
total = left + right
return total + bias
left = 2
right = 3
reading = combined(left, right)
assert(reading, 6)
bias = 4
assert(reading, 9)
left = 5
assert(reading, 12)
- NUC 40 adds an optional wholly untyped bare block header with at least two names and no parameter commas. The final colon and genuine indentation remain mandatory; this is a statement suite, not an arrow expression or an implicit-return form.
def combined left, right:anddef combined(left, right):with this exact body remain valid. Body, call, list, object and anonymous-function punctuation is unchanged; typed/mixed lists still keep their commas.- All other grammar, graph relationships and transactional behavior follow the authoritative section.
Flattened batches retain live object identities
class Task(label):
this.label = label
task1 = Task("alpha")
task2 = Task("beta")
batches = [[task1], [task2, task1]]
flattened = batches.flatMap(batch => batch)
labels = flattened.map(task => task.label)
assert(labels, ["alpha", "beta", "alpha"])
task1.label = "gamma"
assert(labels, ["gamma", "beta", "gamma"])
first = flattened[0]
first.label = "delta"
assert(task1.label, "delta")
assert(labels, ["delta", "beta", "delta"])
task3 = Task("third")
batches.push([task3])
assert(labels, ["delta", "beta", "delta", "third"])
assert(batches.length, 3)
- Accepted NUC 41 adds the same one-level query to lists and class populations. It preserves duplicates, order and identities without mutating the receiver or recursively flattening deeper lists.
- The callback receives one snapshotted item, just as for
map. Real reads, member updates, supplied-argument effects and errors retain their existing reactive and transactional behavior. Returning null alone does not imply the null read made by an explicit intermediate reducer. - Existing map/reduce/concat programs remain valid. See the root contract and the reference for details.
Literal replacement preserves frozen text normalizers
text = "archive::notes::today"
def normalize(value) => value.replaceAll("::", "/")
frozen = normalize.value
live = normalize(text)
snapshot = frozen(text)
assert(live, "archive/notes/today")
assert(snapshot, "archive/notes/today")
def normalize(value) => value.replaceAll("::", "-")
assert(live, "archive-notes-today")
assert(snapshot, "archive/notes/today")
text = "archive::notes"
assert(live, "archive-notes")
assert(snapshot, "archive/notes")
- Accepted NUC 42 adds literal all-occurrences string replacement. The function definition may be live or frozen without freezing its inputs.
- The method never mutates its receiver. Existing
replaceremains first-only; supplied arguments, actual reads and transactions keep their existing rules. - See the root contract and reference for coercion, Unicode boundaries and inherited null/deferred behavior.
Conditional alternatives keep frozen classifiers and live inputs separate
def classify(state, urgent):
if state == "blocked"
return "hold"
else urgent
return "fast"
elif state == "ready"
return "send"
else:
return "wait"
state = "ready"
urgent = false
live = classify(state, urgent)
frozen = classify.value
usingFrozen = frozen(state, urgent)
snapshot = usingFrozen.value
assert(live, "send")
assert(usingFrozen, "send")
urgent = true
assert(live, "fast")
assert(usingFrozen, "fast")
def classify(state, urgent):
if state == "blocked"
return "pause"
else urgent
return "rush"
elif state == "ready"
return "go"
else:
return "queue"
assert(live, "rush")
assert(usingFrozen, "fast")
assert(snapshot, "send")
state = "blocked"
assert(live, "pause")
assert(usingFrozen, "hold")
- NUC 43 adds only an optional spelling for genuine
indented conditional alternatives.
else condition:andelif condition:retain the complete AST and behavior ofelse if condition:. - NUC 49 permits the additional colon-free spellings shown here, including initial and explicit else-if conditions, only at genuine indentation. Colon-bearing, mixed and legacy inline/braced suites remain valid wherever previously supported.
- Function definitions may be live or frozen without freezing their inputs. Ordinary noncapture, explicit returns, receiver scopes and transaction failures are unchanged; no receiver-frame repair is included.
- Explicit
else if, unconditionalelse:/elif:and their accepted colon-free indented final forms remain supported. See the root contract and reference for layout and exact errors.
Compact records keep class rules independent
# Compact records keep class rules independent
class Ticket
first = Ticket()
second = Ticket()
first.amount = 2
second.amount = 5
$Ticket.payload = {reading: $Ticket.amount category: "sample" ready: true}
assert(first.payload.reading, 2)
assert(second.payload.reading, 5)
first.amount = 7
assert(first.payload.reading, 7)
assert(second.payload.reading, 5)
assert(first.payload.ready, true)
- NUC 44 uses the same ordered explicit properties and per-instance receiver reads as the comma-bearing spelling.
- Only a key/colon pair after a fully parsed value can omit its comma.
Shorthand qualification, list/call commas, class rules, aliases,
.valuesnapshots and transaction failures remain unchanged. - The root contract and expression guidance specify the bounded syntax. Comma-bearing forms remain supported.
Compact assertions verify class rules and snapshots
# Compact assertions verify class rules and snapshots
class Sensor
first = Sensor()
second = Sensor()
first.reading = 2
second.reading = 8
$Sensor.active = $Sensor.reading > 5
saved = second.active.value
assert: first.active, false
assert: second.active, true
second.reading = 1
assert: second.active, false
assert: saved, true
saved
# return: true
- Accepted NUC 45 lowers these statement-position checks
exactly to
assert(actual, expected), which remains supported. - Assertions do not install model rules or observers and do not replace the
preceding program result. Class rules,
.valuesnapshots and ordinary expression arguments retain their existing behavior. - Only lowercase
assertfollowed by:in statement position qualifies; existing typed declarations win before this new spelling is considered.
Compact exception suites preserve independent class rules
# Compact exception suites preserve independent class rules
class Task(state: str):
this.state = state
first = Task("open")
second = Task("open")
$Task.closed = $Task.state == "closed"
try
first.state = "closed"
throw "CANCEL"
catch issue
assert(issue, "CANCEL")
assert(first.closed, false)
assert(second.closed, false)
first.state = "closed"
assert(first.closed, true)
assert(second.closed, false)
first.closed
# return: true
- Accepted NUC 46 changes only the optional suite colons.
The same bodies with
try:andcatch issue:retain the complete AST, actual checks, explicit return, rollback and independent reactive rules. - A real indent must qualify each omitted colon separately; inline/braced
forms keep their existing required colons. The native REPL buffers a complete
lexical bare
tryheader through its catch until blank-line commit or EOF.
Reasoning state filters separate supporting facts from derived totals
quantity = 3
subtotal = quantity * 10
total = subtotal + 2
facts = total |> why |> stated
conclusions = total |> why |> derived
assert(facts.map(step => step.node), ["quantity"])
assert(conclusions.map(step => step.node), ["total", "subtotal"])
quantity = 4
assert(facts[0].holds, 4)
assert(conclusions[0].holds, 42)
- The NUC 48 candidate filters the explanation's existing step states without changing their order or live subscriptions.
- The accepted
.filter(step => step.state == "stated")and.filter(step => step.state == "derived")spellings remain supported. - Prefix
stated totalfilters onlytotalitself; it does not include supporting ancestors unless a precedingwhyselects them.
Radian conversion keeps independent headings reactive
heading = 180
target = 90
radians = Math.toRadians(heading)
targetRadians = Math.toRadians(target)
assert(radians, Math.PI)
assert(targetRadians, Math.PI / 2)
heading = 360
assert(radians, Math.PI * 2)
assert(targetRadians, Math.PI / 2)
target = -90
assert(targetRadians, -Math.PI / 2)
- The NUC 50 candidate intrinsically converts the first input
and multiplies it by
PI / 180.0, with division performed first. Finite maxima, signed zeros, subnormals and nonfinite inputs retain that exact composition; NaNs are classified without a payload guarantee. - Omission supplies numeric NaN. Supplied Undefined, including an ignored extra, retains existing deferral. All expression effects/errors and namespace shadowing remain unchanged. The accepted arithmetic remains available; the root specification defines the complete contract.
String iteration keeps independent receiver data separate
class Message(text):
this.text = text
first = Message("ab")
second = Message("CD")
seen = []
for character of first.text:
seen.push(character)
for character of second.text:
seen.push(character)
assert(seen, ["a", "b", "C", "D"])
first.text = "xy"
assert(seen, ["a", "b", "C", "D"])
seen = []
for character of first.text:
seen.push(character)
assert(seen, ["x", "y"])
assert(second.text, "CD")
- Each existing explicit
of/inor implicit-itFor statement evaluates its source once and snapshots one String per Unicode scalar. - Later property updates do not replay the imperative body. Nested iteration
bindings, enclosing
this/self, early returns and transactional failures use the ordinary For machinery, not callbacks or a new iterator protocol. - Empty Strings run no body. Supplementary characters stay whole, while combining marks and joiners remain separate scalars without normalization. Class populations and object-only List traversal are unchanged.
- Split/map remains available; its discarded map results are not a For statement's return value. The root specification defines the candidate contract and preserves legacy syntax.
Copyright 2020 Nucleoid
This document is licensed under the Apache License, Version 2.0. See LICENSE for the full text.
