GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com β€” GitHub - NucleoidAI/Nucleoid: World Model πŸŒ±πŸ‹πŸŒ

BranchesTags
Open more actions menu

Latest commit

 

History

1,328 Commits
1,328 Commits

Folders and files

NameName
Last commit message
Last commit date
.claude
.claude
 
 
.github
.github
 
 
arc
arc
 
 
dataset
dataset
 
 
docs
docs
 
 
examples
examples
 
 
src
src
 
 
tests
tests
 
 
.gitattributes
.gitattributes
 
 
.gitignore
.gitignore
 
 
CODE_OF_CONDUCT.md
CODE_OF_CONDUCT.md
 
 
CONTRIBUTING.md
CONTRIBUTING.md
 
 
Cargo.lock
Cargo.lock
 
 
Cargo.toml
Cargo.toml
 
 
LICENSE
LICENSE
 
 
README.md
README.md
 
 
build.rs
build.rs
 
 
build_statements.rs
build_statements.rs
 
 
clippy.toml
clippy.toml
 
 
nucleoid.spec.md
nucleoid.spec.md
 
 

Repository files navigation

Nucleoid

License Hugging Face Discord

Banner

Synthesized Logic Language for World Models πŸŒ±πŸ‹πŸŒ


⚑ Nucleoid is designed with a minimum-token syntax for logic representation and a declarative execution model, eliminating the need for LLMs to manually manage control flow, state propagation, and other imperative constructs. In addition, Nucleoid is a next-generation logic programming language built on structured objects and their relationships, extending the traditional Knowledge Graph.

  • Near-Deterministic: Structured, reliable reasoning.
  • Minimum-Token Syntax: Token-efficient declarative syntax.
  • Logic Graph: Executable knowledge graph.

Gravity.mp4

Quick Start ⚑

Install the CLI from crates.io:

cargo install nucleoid

Run nucleoid from any directory to open the full-screen terminal workspace. It has top-level tabs (with two reserved for future views), a multiline editor at the bottom, autocomplete for keywords, built-ins and model properties, and in-session input/result history. Click Text, JSON or Table to inspect a result without running the program again. Arrays of objects become table rows; other JSON values have property/value or index/value views.

Enter inserts a newline; F5 runs the whole editor. Blank lines and pasted newlines never submit code or add indentation. Ctrl-J inserts an automatically indented newline when writing a block by hand. Ctrl-Enter also runs on non-Windows terminals that report it distinctly; Windows treats it as a newline because pasted LF produces the same key event. Tab accepts autocomplete (or indents); Ctrl-Space opens suggestions. Ctrl-P / Ctrl-N recalls input while preserving an unfinished draft. F1-F3 changes tabs, F6 changes the result view, and Ctrl-Up / Ctrl-Down selects earlier results. Mouse clicks select tabs, views and results; click/drag edits the cursor/selection, and the wheel scrolls individual panes. Ctrl-C cancels input, Ctrl-Q exits, and .help lists all controls.

nucleoid --plain keeps the line-oriented interface below. Piped stdin or redirected stdout selects it automatically; file execution and the --json and --graph flags keep their existing behavior. Teach it that Socrates is mortal:

> class Human(name: str):
..    this.name = name
..
> $Human.mortal = true
> socrates = Human("Socrates")
> socrates.mortal
true

In plain mode, body lines are prefilled with indentation after the .. prompt. Tab inserts four spaces; use Backspace to dedent and submit a blank line to run the complete block. Piped input is not reindented and must include its own whitespace.

C-style comments

Hash (#) line comments, // line comments and non-nesting /* ... */ blocks all work in files and both interactive interfaces:

total = 12 /* units */ / 2 // half of twelve
# hash comments still work
assert(total, 6)

Comments take delimiter priority outside literals, so // is a comment, not an empty regex; write /(?:)/ for an empty-matching regex (with pattern text (?:), not an empty string). Strings, template text and nonambiguous regexes stay literal, and interpolations read comments too. Comments retain newlines and source positions and cannot join tokens or supply indentation. An unclosed block reports SyntaxError: Unterminated block comment at its opening slash. Plain input buffers an open block across blank lines until it closes or EOF. They no longer need the removed --c-style-comments flag; see NUC 76.

Is assignments

An assignment can also be written target is value, with quotes optional around a single word:

status is OPEN
label is "shipped"
assert([status, label], ["OPEN", "shipped"])
OPEN = "open"
assert(status, "open")
delete OPEN
assert(status, "OPEN")

a is 5, o.x is "good" and this.total is total * 2 mean their = spellings. A value of one bare word reads the variable, class, function or built-in of that name when one exists, and otherwise stores its own text, so status above follows OPEN while it is defined. is comparisons such as if status is OPEN: keep working inside conditions and parentheses. It no longer needs the removed --is-assignment flag, so a is good assigns rather than reading as three statements; see NUC 93.

Conditional words

A branch of a conditional is value can be a bare word too:

ready = false
status is OPEN if ready else WAITING
assert(status, "WAITING")
ready = true
assert(status, "OPEN")

a is A if c else B and a is c ? A : B read each branch that is one bare word as a word value is read: the variable, class, function or built-in of that name when one exists, otherwise its own text. Conditions and parenthesized operands are not words, so a is (A if c else B) needs A and B to be defined. It no longer needs the removed --conditional-words flag; see NUC 94.

Item words

A list item, a dictionary value or a named argument inside an is value can be a bare word too:

colors is [RED, GREEN]
pair is {left: RED, right: BLUE}
assert([colors, pair], [["RED", "GREEN"], {"left": "RED", "right": "BLUE"}])
RED = "red"
assert([colors[0], pair.left], ["red", "red"])

colors is [RED, GREEN], glow is {small: DIM, large: BRIGHT}[size] and first is Light(color=RED, parts=[GREEN, GREEN]) read each item, dictionary value and named argument that is one bare word as a word value is read, wherever the list, dictionary or call stands in the value, outside function bodies, comprehension elements, template interpolations and inner assignments. Keys keep their text, {A} still reads the variable A, a parenthesized item such as [(A)] is a reference, and a conditional item such as [A if c else B] has word branches. It needs no flag, and = keeps the reference reading, so colors = [RED, GREEN] needs RED and GREEN to be defined; see NUC 95.

At variables

Inside an is value, @name reads the variable name, never the word:

a is 1
b is @a + 2
RED = "red"
colors is [@RED, GREEN]
assert([b, colors], [3, ["red", "GREEN"]])

@a is read exactly as (a) is, so b is @c + 3 raises ReferenceError("c is not defined") while nothing defines c, and so does b is @c, where b is c would store the word "c". The mark works wherever an operand begins in an is value, outside function bodies, comprehension elements, template interpolations, inner assignments and targets, and in the right operand of an is comparison, as in if state is @OPEN:. Everywhere else a leading @ is still a syntax error, x@first is still a version, and b is @first still reads as b followed by is@first; see NUC 96.


Design Theory πŸ“

World Models, Neuro-Symbolic AI and AI Language

Language Reference

docs/README.md is the language reference: statements and state, variables and dependencies, expressions, types and instances, properties, class-level rules, blocks and scope, control flow, functions, transactions, built-in objects, live graph reasoning, error messages, and a syntax summary.

It is assembled from the NUC documents in docs/, indexed by NUC 0 with the conventions in NUC 1. nucleoid.spec.md is normative; where the two disagree, the specification wins.

docs/examples.md covers the same ground as complete programs, each one runnable as written.

The isolated NUC 47 revision uses ordinary class headers: class Point(x, y) accepts positional, named and positional-then-named calls. Point(y=4, x=3) binds labels without caller assignments; explicit grouped assignments retain positional effects. The former star header is removed. See the paired reference example. Ordinary function/method calls retain assignment-expression behavior.

A Nucleoid program is a set of statements that remain true. An assignment is not an instruction that runs once and finishes, it is a relationship the runtime records and maintains.

An assignment states a relationship, not a result

a = 1
b = a + 2

a = 3

assert(b, 5)

b is never stale. It is the sum of a and 2, so changing a brings it up to date, and the same holds for properties, class-level rules and everything else the reference covers.

Compound assignments keep their reactive relationships

amount = 10
step = 2
amount += step
assert(amount, 12)
step = 3
assert(amount, 15)

The compound operators +=, -=, *=, /=, and %= are short spellings of target = target op (expression), not a new mutation mode. Plain names and dotted paths are supported; old assignment syntax remains valid. NUC 13 passed independent per-case evaluation. The crate is now 2.0.0 because the new public lexer token variants affect downstream exhaustive Rust matches; existing Nucleoid programs remain valid.

Empty types need no placeholder body

class Entity
entity1 = Entity()
$Entity.enabled = true
assert(typeof Entity, List)
assert(entity1.enabled, true)
assert(Entity.length, 1)

A bare class header is equivalent to an indented pass-only body. Optional parameters keep their existing binding behavior and do not create properties automatically. Nonempty bodies and inheritance retain the colon syntax. NUC 14 specifies the accepted syntax and its independent per-case evaluation.

Parent constructors omit empty call parentheses

class Counter:
    def init():
        this.total = 1

class Counted: Counter
    def init():
        super
        this.total = this.total + 1

counted = Counted()

assert(counted.total, 2)

Bare super is the zero-argument expression super(), running the parent constructor on the same instance and returning null. Calls with arguments still use super(arguments...). NUC 16 describes this candidate shorthand; its proposal status remains Draft.

Object fields reuse their identifier names

amount = 4
label = "meter"
reading = { amount, label }

assert(reading.amount, 4)
assert(reading.label, "meter")

amount = 6

assert(reading.amount, 6)

In this isolated Draft NUC 17 candidate, an ordinary identifier field in an object expression is shorthand for name: name. Explicit fields still mix with shorthand; quoted and numeric keys still need a value. The existing AST, canonical expanded source, scope lookup, dependencies, snapshots, and transactions are unchanged. This prototype does not change the accepted language until independent evaluation and caller integration.

Single input named arrows omit parameter parentheses

def square amount => amount * amount

assert(square(3), 9)
assert(square(4), 16)

NUC 18 specifies parameter-parenthesis omission for named arrow definitions and methods with exactly one untyped ordinary identifier parameter. Nullary definitions and calls still use parentheses; the optional untyped and typed/mixed multiple-parameter arrow spellings are demonstrated below. def square(amount) => amount * amount remains valid. The body, binding, reactivity, snapshots, receivers and initializer behavior remain those of NUC 12.

Single input block definitions omit parameter parentheses

def square amount =>
    amount * amount

assert(square(3), 9)
assert(square(4), 16)

NUC 19 specifies the same optional single-untyped-identifier spelling before an indented colon suite, including methods and init. The sole-return example above uses NUC32's equivalent indented arrow; def square amount: or def square(amount): with explicit return amount * amount remains valid. The colon must lead to a real indented body; inline/brace suites, empty and typed/mixed multiple-parameter block signatures retain their parentheses. The optional untyped multiple-parameter indented spelling is demonstrated below. Typed single-parameter indented blocks have the separate optional spelling below. Block returns, parameter binding, live dependencies and runtime behavior are unchanged.

Typed single input named arrows omit parameter parentheses

source = 3

def double amount: int => amount * 2

result = double(source)

assert(result, 6)

source = 5

assert(result, 10)

NUC 20 specifies only the immediate single-identifier typed-arrow header. def double(amount: int) => amount * 2 remains valid with identical parameter/type metadata and named return-block behavior. Annotations retain their existing handling, not new variable lookup or type enforcement. Typed bare inline/braced blocks and logical header breaks are not added. The separate NUC29 extension below adds only typed/mixed multiple-parameter arrows; calls and NUC18/19 spellings are unchanged.

Numeric clamping keeps values within live bounds

reading = 12
upper = 10

bounded = Math.clamp(reading, 0, upper)

assert(bounded, 10)

reading = 3

assert(bounded, 3)

upper = 2

assert(bounded, 2)

NUC 21 specifies static numeric clamping as the ordered composition Math.min(Math.max(value, lower), upper) with existing numeric coercion. Reversed bounds yield the upper bound, numeric NaN propagates, and omitted inputs supply numeric NaN. Extra argument expressions still run left to right and participate in the existing undefined gate. The proposal header records its status; no range-validation error or callable Math method reference is introduced.

Numeric interpolation blends live values

start = 0
end = 10
weight = 0.5

mixed = Math.lerp(start, end, weight)

assert(mixed, 5)

end = 20

assert(mixed, 10)

weight = 0.25

assert(mixed, 5)

NUC 22 specifies static Math.lerp(start, end, weight); its header records its status. After intrinsic numeric conversion, it computes end - start, multiplies by weight, then adds start, using ordinary ordered arithmetic without fused operations, endpoint shortcuts or weight clamping. Negative or greater-than-one weights extrapolate; special values and rounding follow that exact formula. Omissions supply numeric NaN; every supplied expression runs once left to right, including extras and the existing undefined gate. Live-null/deferred assignment contexts, shadowing and unsupported Math method-value reads remain unchanged.

Unit-interval saturation normalizes live progress

# Unit-interval saturation normalizes live progress

ratio = 0.3
progress = Math.saturate(ratio)

assert(progress, 0.3)

ratio = 1.5

assert(progress, 1)

ratio = -0.5

assert(progress, 0)

NUC 23 specifies static Math.saturate(value) as exactly ordered Math.clamp(value, 0, 1) through the accepted min/max folds, with intrinsic numeric conversion, not host range validation or endpoint shortcuts. Omission produces numeric NaN; extra expressions still run once left to right and retain reads, effects and the existing undefined gate. Live-null/deferred handling, rollback, namespace shadowing and unsupported method-value reads remain unchanged. The normative contract is Reactive unit-interval saturation.

Typed block definitions preserve live bounded progress

# Typed block definitions preserve live bounded progress

ratio = 0.3

def normalize amount: ArbitraryType =>
    Math.saturate(amount)

progress = normalize(ratio)

assert(progress, 0.3)

ratio = 1.5

assert(progress, 1)

ratio = -0.5

assert(progress, 0)

NUC 24 describes the optional single typed parameter spelling before a real indented suite. def normalize(amount: ArbitraryType): with the same body remains valid. The immediate name/colon/annotation/colon prefix and real indent preserve the full parameter metadata and named block AST. Annotations introduce no lookup or runtime type constraint. Header-line breaks, inline/braced bare suites and other parameter/type forms are not added; calls, NUC18/19/20 spellings, block returns and live dependencies are unchanged.

Logical compound assignments preserve short-circuiting

def fail():
    throw "UNREACHABLE"
enabled = true
enabled ||= fail()
assert(enabled, true)
disabled = false
disabled &&= fail()
assert(disabled, false)

Accepted NUC 26 defines optional &&= and ||=. They lower exactly to target = target && (rhs) and target = target || (rhs), not JavaScript-style conditional writes. A standing assignment installs its relationship even when logical evaluation skips the right operand. Existing =, &&, ||, and, or, and arithmetic compounds remain supported; snapshots, dependencies and rollback are unchanged. The public lexer enum additions require a human-owned API semver decision; this revision changes no project version.

Named arrows can omit parentheses around multiple parameters

def add left right => left + right
assert(add(2, 3), 5)
def scaled left right factor => (left + right) * factor
assert(scaled(2, 3, 4), 20)

Accepted NUC 27 adds only a complete comma-separated list of two or more untyped ordinary identifiers immediately followed by => on the same logical header line, including methods and init. def add(left, right) => left + right remains valid and exactly equivalent. Trailing bare commas, bare inline/braced blocks, defaults, destructuring, rest parameters and return annotations are not added. Typed/mixed arrow lists have the separate NUC29 spelling below. No logical newline, semicolon or comment-induced break is skipped. Calls retain their parentheses and anonymous arrows retain their existing grammar; the single-parameter typed and block alternatives above remain unchanged.

Indented blocks can omit parentheses around multiple parameters

def add left right =>
    left + right
assert(add(2, 3), 5)

NUC 28 adds the optional spelling of def add(left, right): with the same indented body, including methods and init. The complete comma-separated list contains two or more untyped ordinary identifiers on one logical header line. After the colon require an immediate newline or indent and a real indent after only normal newline/semicolon layout; a colon immediately followed by a semicolon does not qualify. Blank/comment lines before a genuine body keep their existing grammar. NUC30 separately adds typed/mixed multiple indented headers below; NUC28's identifier-only forms remain unchanged. Inline/braced bare suites, trailing bare commas and logical breaks within the header remain unsupported. Parenthesized forms, arrows, single-parameter guards and calls are unchanged. Block returns, scope, receivers, live relationships, snapshots and rollback retain the parenthesized behavior; there is no implicit last-expression return.

Typed multiple parameter named arrows can omit parentheses

def add left: int, right: int => left + right
assert(add(2, 3), 5)
  • NUC 29 adds this optional arrow spelling; def add(left: int, right: int) => left + right remains valid and exactly equivalent, including methods and init.
  • Require at least two ordinary identifier parameters, with at least one existing : Identifier annotation for the new typed/mixed forms. Untyped multiple lists retain NUC27 behavior; annotations stay metadata, not runtime type checks. Mixed lists may annotate any parameter.
  • The complete comma-separated header must immediately end in =>, without logical breaks or skipped separators. NUC29 does not itself add blocks; NUC30's separate typed/mixed indented spelling follows below. Bare inline/braced suites, trailing bare commas, new parameter/type grammar and call-site omission remain unsupported. Single guards and NUC28 untyped blocks are unchanged.
  • Binding, named return-block lowering, expression bodies, receivers, reactivity, snapshots, graph dependencies and rollback remain unchanged.

Typed multiple parameter blocks can omit parentheses

def add left: int, right: int =>
    left + right
assert(add(2, 3), 5)
  • NUC 30 describes the optional spelling of def add(left: int, right: int): with the same real indented body, including methods and init. Mixed headers such as def add left: int, right: also preserve the complete named block and ordered annotation metadata.
  • Require at least two ordinary identifier parameters, with at least one existing : Identifier annotation for the new forms. Entirely untyped lists remain NUC28 behavior; annotations introduce neither lookup nor type checking.
  • Scan the complete header without consuming tokens or skipping logical breaks. The suite colon must immediately lead to a newline or indent and a real indent after only normal newline/semicolon layout; :; never qualifies. A final untyped parameter's colon belongs to the suite, not an annotation.
  • Parenthesized forms, NUC24 single typed blocks, NUC28 untyped blocks, NUC29 arrows and calls remain unchanged. Bare inline/braced suites, trailing commas, new type/parameter grammar and return annotations are not added.
  • Block returns, local shadowing, receivers, reactive inputs, live aliases, definition snapshots, redefinition and rollback keep the parenthesized behavior, with no implicit final-expression return.

Named source loops can bind it implicitly

class Task
task = Task()
task.done = false
for Task:
    it.done = true
assert(task.done, true)
  • Accepted NUC 31 specifies optional for NAME: with a genuine indented body as exactly for it of NAME: with that same body. NAME is one ordinary identifier; the body binds each visited value to local it, restoring any outer binding afterward.
  • The source is evaluated once: class populations and lists visit only current registered typed instances in order. Primitives, null elements and plain objects in lists are skipped; this is not general array iteration or a standing rule. A named noniterable source raises the existing TypeError.
  • The NUC 51 candidate additionally accepts String sources as scalar snapshots, without changing that object-only List policy.
  • Require the immediate identifier/colon header, then a newline or indent and a real indent after only normal newline/semicolon layout. No :;, inline/braced/missing-indent or expression-source shorthand is added.
  • Explicit for variable of expression and for variable in expression remain valid with their existing arbitrary expressions and suite forms. Nested scopes, receivers, returns, snapshots, graphs and rollback are unchanged.

Reasoning stays live too: why b explains its dependencies, and affects a reports its downstream impact. Both update when relationships are rewritten, added, or removed, even if their values stay the same. The implemented stages of NUC 11 cover selections, pipelines, and structural subscriptions; its later stages remain proposals.

Every example in the reference is executable: tests/reference.md is its executable form and runs under cargo test, as do the snippets on this page.


Welcome! I’ve been expecting youβ€”"Skynet was gone. And now one road has become many." 🌐

The future is building up! World Models are now an emerging field within AI communities and marks a crucial milestone on the journey to AGI. Unfortunately, existing symbolic AI and knowledge graphs lack advancement in today's AI landscape. Nucleoid is revolutionizing knowledge graphs with declarative, logic-based, contextual runtime, which can be integrated with ANNs to lay a robust foundation for the next leap forward imao.

Can Mingir 
@canmingir


Neuro-Symbolic AI

AI Architecture

A world model is what a system knows about a domain: which entities exist, how they relate, which rules hold across all of them, and what follows once something changes. In an LLM that model is implicit, spread across the weights, and that is where hallucination begins, because a model that cannot be inspected cannot be corrected and cannot be held to its own rules. Neuro-Symbolic AI is how the model is made explicit, and it is why the two components below are complementary rather than competing.

Neuro-Symbolic AI is an approach that integrates the strengths of both neural networks and symbolic AI to create systems that can learn from data and also reason logically. By combining these two components, Neuro-Symbolic AI aims to leverage the intuitive, pattern-recognition capabilities of neural networks along with the logical, rule-based reasoning of symbolic AI. This integration offers a more holistic AI system that is both adaptable and able to explain its decisions, making it suitable for complex decision-making tasks where both learning from data and logical reasoning are required. Here’s how it breaks down:

Neural Networks: The Learning Component

Neural networks in Neuro-Symbolic AI are adept at learning patterns, relationships, and features from large datasets. These networks excel in tasks that involve classification, prediction, and pattern recognition, making them invaluable for processing unstructured data, such as images, text, and audio. Neural networks, through their learning capabilities, can generalize from examples to understand complex data structures and nuances in the data.

Symbolic AI: The Reasoning Component

The symbolic component of Neuro-Symbolic AI focuses on logic, rules, and symbolic representations of knowledge. Unlike neural networks that learn from data, symbolic AI uses predefined rules and knowledge bases to perform reasoning, make inferences, and understand relationships between entities. This aspect of AI is transparent, interpretable, and capable of explaining its decisions and reasoning processes in a way that humans can understand.


Neuro-Symbolic Diagram

World Models: The State Component

Neural networks learn and symbolic AI reasons, but reasoning needs something to reason over, and that is the world model: the entities, relationships and rules a system currently holds to be true. In Nucleoid the model is not a passive knowledge base that is read from and written to, it is a logic graph that the runtime keeps true on its own. A rule stated over a type holds for every instance of it, including instances created long afterwards, and a change to any value propagates to everything derived from it, so the model is never left holding a fact together with its own stale consequence.

Rules also decide which worlds are admissible. A statement and every rule it triggers form a single transaction, and if any rule rejects the change, the transaction is rolled back and the state is exactly as it was, so an update that would contradict the model is never partially applied. A world model built this way cannot drift into a state that violates its own laws.

This is what makes the model usable by a language model. It is written incrementally, one statement at a time, in a syntax that costs few tokens, and it can be queried long after it was written, by another session or another model, because the meaning lives in the graph rather than in the context window.

Declarative Language: 6GL Programming Language

Nucleoid is a 6GL programming language, and Nucleoid acts as the ubiquitous language in Neuro-Symbolic AI for specifying the desired outcomes of a program without detailing the procedural methods to achieve these outcomes. This type of language is essential for articulating logical rules, constraints, and relationships that underpin symbolic reasoning within these systems. It supports the formulation of structured knowledge bases and facilitates logical reasoning tasks, enabling systems to deduce, infer, and respond to queries based on established rules. Moreover, declarative languages are instrumental in integrating the outputs of neural networks into symbolic reasoning frameworks, marrying data-driven learning with rule-based logic. Their widespread use enhances the transparency, explainability, and modularity of AI systems, while also boosting their efficiency in domains heavily reliant on rule-based operations.

Declarative Logic in Symbolic Reasoning

Declarative logic is a subset of declarative programming, a style of building programs that expresses the logic of a computation without describing its control flow. In declarative logic, you state the facts and rules that define the problem domain. The runtime environment or the system itself figures out how to satisfy those conditions or how to apply those rules to reach a conclusion. This contrasts with imperative programming, where the developer writes code that describes the exact steps to achieve a goal.

Symbolic reasoning refers to the process of using symbols to represent problems and applying logical rules to manipulate these symbols and derive conclusions or solutions. In AI and computer science, it involves using symbolic representations for entities and actions, enabling the system to perform logical inferences, decision making, and problem-solving based on the rules and knowledge encoded in the symbols.

By integrating Nucleoid into Neuro-Symbolic AI, the system benefits from enhanced interpretability and reliability. The declarative logic and rules defined in Nucleoid provide clear explanations for the AI's decisions, making it easier for users to understand and trust the system's outputs. Furthermore, the explicit reasoning capabilities help ensure that decisions are made based on logical principles, adding a layer of reliability and consistency to the AI's behavior.

Graph Animation

Plasticity in Neuro-Symbolic AI

In the realm of Neuro-Symbolic AI, Plasticity is an important element for the system's ability to modify and optimize its connections in response to new information. This concept is inspired by neuroplasticity in biological brains, where neurons can strengthen or weaken their connections based on activity, enabling learning and memory.

  1. Dynamic Knowledge Base: Neuro-Symbolic AI systems dynamically update their knowledge base as they encounter new scenarios or corrections to their previous knowledge. This continuous updating process allows the systems to remain relevant and accurate over time.
  2. Adaptive Logic and Reasoning: The AI system can modify its symbolic rules and reasoning strategies to better match observed data or outcomes, enhancing its decision-making and problem-solving abilities.
  3. Generalization and Specialization: Through plasticity, the system can generalize from learned experiences to new, unseen scenarios or specialize in certain domains by fine-tuning its parameters or rules based on specific data inputs.

The idea of plasticity in AI, especially in logic-based systems like those in Neuro-Symbolic AI, enhances the capability of machines to not only perform tasks based on fixed rules but also to evolve those rules and adapt their reasoning over time, much like the cognitive flexibility of living things.

Learn more at nucleoid.com/docs/get-started

Nucleoid's Taxonomy

Nucleoid is an implementation of symbolic AI for declarative (logic) programming at the runtime. As mentioned, the declarative runtime environment manages the state and stores each transaction in the built-in data store by declaratively rerendering statements and building the knowledge graph (base) as well as an execution plan.

Nucleoid's Taxonomy

The declarative runtime isolates a behavior definition of a program from its technical instructions and executes declarative statements, which represent logical intention without carrying any technical detail. In this paradigm, there is no segregation regarding what data is or not, instead approaches how data (declarative statement) is related with others so that any type of data including business rules can be added without requiring any additional actions such as compiling, configuring, restarting as a result of plasticity. This approach also opens possibilities of storing data in the same box with the programming runtime.

Logical Diagram 1 Logical Diagram 2

In short, the main objective of the project is to manage both of data and logic under the same runtime. The declarative programming paradigm used by Nucleoid allows developers to focus on the business logic of the application, while the runtime manages the technical details.This allows for faster development and reduces the amount of code that needs to be written. Additionally, the sharding feature can help to distribute the load across multiple instances, which can further improve the performance of the system.

Named arrows can indent a single expression

def square value =>
    value * value
assert(square(4), 16)

NUC 32 specifies an optional genuinely indented expression after the existing named =>. It is exactly the colon suite containing return value * value; existing inline arrows and colon suites remain valid. It does not add an arbitrary statement suite or an implicit final-statement return, and nullary definitions still require (). Earlier sole-return block examples above use this equivalent layout where applicable; existing colon suites, including multi-statement bodies, are unchanged alternatives.

Cubic smoothstep keeps input changes live

value = 0.25
unit = Math.saturate(value)
eased = Math.smoothstep(0, 1, value)
assert(unit, 0.25)
assert(eased, 0.15625)
value = 0.75
assert(eased, 0.84375)
  • Accepted NUC 33 defines static Math.smoothstep(lower, upper, value), with bounds before the value.
  • The independent unit control remains live; the intrinsic normalizes and clamps before applying the ordered cubic, retaining existing special-value, deferred-read and transactional behavior.

Indented else suites keep live inputs concise

enabled = false
raw = 2
if enabled:
    display = raw
else
    display = raw + 1
assert(display, 3)
enabled = true
assert(display, 2)
raw = 5
assert(display, 5)
  • Accepted NUC 34 permits this optional final-else spelling only before a real indented body. else: remains valid and equivalent.
  • This legacy control deliberately retains its condition colon. NUC 49 also permits omission for genuine-indented if/else if suites; inline, semicolon-separated and braced alternatives still require their accepted colons. A header-line semicolon cannot qualify.
  • Nested branch association, live inputs, scope, returns and rollback are unchanged. Strings and comments containing else: are not rewritten.

Quintic smootherstep keeps input changes live

value = 0.25
unit = Math.saturate(value)
eased = Math.smootherstep(0, 1, value)
assert(unit, 0.25)
assert(eased, 0.103515625)
value = 0.75
assert(eased, 0.896484375)
  • Accepted NUC 35 adds static Math.smootherstep(lower, upper, value) beside unchanged cubic Math.smoothstep. The independent unit control remains live.
  • Normalize and clamp before the exactly ordered quintic; do not clamp its result, which can round beyond the unit interval. Existing numeric coercion, special values, deferred reads, argument effects and rollback are preserved.

Floor fractional parts keep negative inputs live

value = -1.25
whole = Math.floor(value)
fraction = Math.fract(value)
assert(whole, -2)
assert(fraction, 0.75)
value = 3.5
assert(fraction, 0.5)
  • Accepted NUC 36 adds static Math.fract(value) as intrinsic numeric conversion followed by floor and subtraction, retaining the independent whole control and accepted floor-subtraction spelling.
  • Negative fractions subtract floor, not truncation. Tiny negative inputs can round to exactly one, so the result is not promised strictly in [0, 1). Numeric coercion, ignored-extra effects/reads, deferral and rollback remain.

Named arrow parameters can omit every comma

def add left right => left + right
assert(add(2, 3), 5)
def scaled left right factor => (left + right) * factor
assert(scaled(2, 3, 4), 20)
  • NUC 37 adds optional all-comma-free bare named arrows with at least two untyped ordinary identifiers on one logical header line. The comma-bearing and parenthesized forms remain valid. Typed/mixed lists still need commas; the separate NUC40 below adds the wholly untyped, genuinely indented colon-suite counterpart.

Inverse interpolation keeps live inputs unclamped

start = 0
end = 8
sample = 2
weight = Math.inverseLerp(start, end, sample)
assert(weight, 0.25)
sample = 12
assert(weight, 1.5)
end = 16
assert(weight, 0.75)
  • Accepted NUC 38 adds only static Math.inverseLerp(start, end, value), the unclamped ordered composition (value - start) / (end - start) with intrinsic numeric conversion.
  • Descending ranges and results outside [0, 1] are intentional. Equal endpoints, nonfinite values and span overflow follow separate subtraction, subtraction and division, without endpoint shortcuts or scaling.
  • The accepted arithmetic spelling remains available. Ignored-extra reads, effects, strict failures, live-null deferral and rollback remain unchanged; the authoritative contract defines the complete numeric and compatibility rules.

Range remapping keeps live values unclamped

start = 0
end = 8
targetStart = 0
targetEnd = 100
sample = 2
mapped = Math.remap(start, end, targetStart, targetEnd, sample)
assert(mapped, 25)
sample = 12
assert(mapped, 150)
end = 16
assert(mapped, 75)
  • Accepted NUC 39 adds only static Math.remap(start, end, targetStart, targetEnd, value), the unclamped numeric composition of accepted inverse interpolation and interpolation.
  • Keep six separate ordered operations, with no rescaling, range ordering, endpoint shortcuts or fused arithmetic. Both ranges may descend.
  • The accepted nested spelling remains available. Its target arguments run before inner source arguments; the new call instead evaluates its listed five arguments left to right. Pure-read paired cases preserve behavior.
  • Ignored-extra effects/reads, live-null deferral and rollback use unchanged machinery; the authoritative contract gives the full rules.

Indented blocks can omit every parameter comma

def add left right:
    return left + right
left = 2
right = 3
total = add(left, right)
assert(total, 5)
left = 5
assert(total, 8)
  • NUC 40 adds only the optional complete def add left right: header before a real indented statement suite. def add left, right: and def add(left, right): with the same body remain valid and equivalent.
  • Require at least two wholly untyped ordinary identifiers and omit all parameter commas or none. The final colon and real indentation are required; typed/mixed lists, inline/braced bare suites, calls and anonymous functions do not gain comma omission. Explicit, bare and absent returns are unchanged.
  • The authoritative contract keeps all accepted syntax, body punctuation, live dependencies and rollback.

Reactive flat mapping

Flat mapping produces zero or many results per input

numbers = [1, 2, 3]
def copies(value):
    if value > 1:
        return [value, value * 10]
    return []
expanded = numbers.flatMap(copies)
assert(expanded, [2, 20, 3, 30])
assert(numbers, [1, 2, 3])
numbers.push(4)
assert(expanded, [2, 20, 3, 30, 4, 40])
  • Accepted NUC 41 provides one read-only flatMap(callback) query for lists and class populations.
  • Each snapshotted item invokes the callback once, in order, with one argument. A returned list contributes one level of items; other values contribute one item. Order, duplicates and live object/function identities remain.
  • Callback validation, eager supplied arguments (including extras), real null/deferred reads, dependencies and transactional errors use existing machinery. Functions and properties named flatMap remain ordinary names.
  • Existing map, reduce and concat stay supported. See the authoritative contract for snapshot timing and the distinction between returning null and actually reading it.

Reactive literal string replacement

Literal replacements normalize every occurrence

source = "todo:one|todo:two|todo:one"
normalized = source.replaceAll("todo:", "")
assert(normalized, "one|two|one")
source = "todo:three"
assert(normalized, "three")
assert(source, "todo:three")
  • Accepted NUC 42 adds a read-only string-instance replaceAll(search, replacement) for literal, non-overlapping matches.
  • Replacement text is literal too; empty search inserts at Unicode scalar boundaries, including both ends (one boundary for an empty receiver).
  • Inputs retain ordinary reactive reads, snapshots, argument evaluation and transactional errors. Existing first-only replace and split/join recipes remain valid. See the root contract.

Direct conditional alternatives

Conditional alternatives omit the second keyword

def label(state):
    if state == "done"
        return "archive"
    else state == "ready"
        return "send"
    else:
        return "wait"
state = "ready"
result = label(state)
assert(result, "send")
state = "done"
assert(result, "archive")
state = "queued"
assert(result, "wait")
  • NUC 43 optionally omits the second if only before a complete condition with a genuine indented body. elif condition: is the same optional spelling through the existing elif alias for else.
  • NUC 49 additionally makes the header colon optional for initial if, explicit else if/elif if, and these direct alternatives, only before a genuine indented suite. The CLI recognizes initial colon-free headers and autoindents without treating literal/comment punctuation as syntax.
  • Explicit else if remains supported, including inline/braced bodies. Final else:/elif: and genuine-indented colon-free final alternatives retain their unconditional meaning. No standalone or inline direct conditional alternative is added.
  • Parsing lowers to the same nested conditional, keeping first-match evaluation, live dependencies, scopes, returns and rollback unchanged. See the root contract.

Comma-free explicit object properties

Explicit object fields omit redundant commas

# Explicit object fields omit redundant commas

name = "sensor"
reading = 4
record = {name: name reading: reading unit: "C"}
assert(record.name, "sensor")
assert(record.reading, 4)
reading = 9
assert(record.reading, 9)
  • NUC 44 permits the next explicit string, identifier or numeric key followed immediately by : after the preceding value has been fully parsed. Existing commas retain priority.
  • Comma-bearing forms remain valid; shorthand, lists, calls, parameters and block syntax do not gain general comma omission. Canonical records still render with quoted keys and commas.
  • Live dependencies, aliases, snapshots, class receivers and transaction behavior remain unchanged. See the root contract.

Colon-led assertion statements (Accepted NUC 45)

Assertions have a compact statement spelling

# Assertions have a compact statement spelling

status = "ready"
record = {status: status}
assert: record.status, "ready"
status = "sent"
assert: record.status, "sent"
  • Accepted NUC 45 adds an optional statement spelling for the existing two-argument assert(actual, expected) helper.
  • The old call syntax remains supported, including expression uses. This is an equality check, not a truthiness assertion or generic bare call.
  • Both forms use the same AST, evaluation order, failure collection, counters, transactions and exclusion from program results and model nodes. See the root contract.

Colon-free indented exception suites (Accepted NUC 46)

Indented error recovery omits redundant colons

# Indented error recovery omits redundant colons

quantity = 3
total = quantity * 2
try
    quantity = 5
    throw "CANCEL"
catch error
    assert(error, "CANCEL")
assert(quantity, 3)
assert(total, 6)
  • Accepted NUC 46 permits either exception-suite colon to be omitted only when its complete existing header has a real indented body. try: and catch error: with the same bodies remain valid and equivalent.
  • Default catch, named catch issue and parenthesized catch(issue) keep the existing bindings. Inline/braced suites still require their colons.
  • The native REPL buffers a complete lexical bare try header through its catch until blank-line commit or EOF, including trailing header comments.
  • Recovery uses the same transactional rollback and live dependency model, not a new error API, scope or continuation rule. See the root contract.

Degree to radian conversion follows live inputs

degrees = 180
radians = Math.toRadians(degrees)
assert(radians, Math.PI)
degrees = 90
assert(radians, Math.PI / 2)
  • The NUC 50 candidate adds only static Math.toRadians(value), retaining the accepted value * (Math.PI / 180) arithmetic. Intrinsic conversion multiplies by the separately computed f64 factor PI / 180.0, not by PI before division.
  • Supplied expressions, including ignored extras, retain left-to-right evaluation, reactive reads, errors and the existing supplied-Undefined gate. See the root contract.

String iteration

String iteration reads characters directly

word = "NUC"
letters = []
for character of word:
    letters.push(character)
assert(letters, ["N", "U", "C"])
  • The NUC 51 candidate extends the existing of, in and named-source implicit-it loops to Strings, without new syntax.
  • Evaluate the source once and snapshot one String per Unicode scalar in source order; an empty String runs no body. Bodies remain one-shot and imperative, with ordinary scopes, receivers, returns and rollback.
  • This is not byte, UTF-16 or grapheme traversal and does not normalize text. Primitive lists still skip their entries; split/map expressions remain available but return different intermediate values. See the root contract.

Read-only state versions

Named state versions read earlier variable values

name = "Can"
name = "Daphne"
firstName = name@first
lastName = name@last
previousName = name@previous
assert(firstName, "Can")
assert(lastName, "Daphne")
assert(previousName, "Can")
  • NUC 53 defines automatic retention of actual changes to variable and object-property storage, including reactive updates. @0 is the first version; @first, @last and @previous select literal positions without mirror lists. Equal writes add nothing.
  • Selectors are read-only, oldest-first and never negative. Histories persist until deletion/clear and roll back with state. Returned objects keep normal identity semantics, not deep snapshots. Ordinary fields named first, last and previous, negative list indexes and .value remain unchanged. See the root contract.

Literal parameter defaults

Literal defaults fill omitted trailing arguments

def total(quantity, unit = 5) => quantity * unit
one = total(1)
two = total(2)
three = total(3)
assert(one, 5)
assert(two, 10)
assert(three, 15)
  • Parenthesized user-defined signatures may supply immutable scalar literals for absent arguments. Explicit null/falsy arguments are not replaced; annotations and bare signatures keep their existing behavior.
  • Defaults are finite signed numbers, quoted Strings, Booleans or null, not expressions or shared mutable values. See the root contract.

Class-level state version anchors

Class conditionals shift each instance from its first recorded coordinates

class Obj(x: int, y: int, orientation: int):
    this.x = x
    this.y = y
    this.orientation = orientation
if $Obj.orientation == 180:
    $Obj.tag = "l4214"
    $Obj.x = $Obj.x@first - 1
    $Obj.y = $Obj.y@first - 1
first = Obj(10, 20, 180)
second = Obj(30, 40, 180)
assert([first.x, first.y, first.tag], [9, 19, "l4214"])
assert([second.x, second.y, second.tag], [29, 39, "l4214"])
first.orientation = 0
first.orientation = 180
assert([first.x, first.y], [9, 19])
assert([first.x@0, second.x@first], [10, 30])
  • Draft NUC 55 extends accepted NUC53 histories to stable class self-anchors: @first means the instance/property slot's first recorded value, not the value when its rule activates. @0 is the same anchor.
  • Ordinary class property rules can use fixed self-history too. Missing fixed self-entries wait for actual input. Moving @last/@previous reads retain accepted imperative conditional effects; direct moving self-rules still raise Circular Dependency. Histories stay read-only, slot-owned and transactional. See the root nucleoid.spec.md Class-level state version anchors contract.
  • With NUC 85, a class rule may also read another property of its own receiver at a fixed position, so $Box.width = $Box.height@first with $Box.height = $Box.width@first swaps each box's first recorded values. Such a cycle stands only when every read on it is fixed; a live or moving read still raises Circular Dependency. Fixed reads stay tracked through deletion and recreation. See Fixed cross-property class anchors in nucleoid.spec.md.

Reactive base two powers

level = 2
multiplier = Math.exp2(level)
assert(multiplier, 4)
level = 5
assert(multiplier, 32)
  • Accepted NUC 56 names Math.exp2(value) while retaining the exact accepted Math.pow(2, value) numeric operation and reactive call behavior. Ordinary Math.pow remains supported.
  • The NUC 100 candidate adds unshadowed static Math.cot(value), exactly the existing 1 / Math.tan(value) in radians: intrinsic conversion, tangent, then reciprocal with the same rounding. Missing-first NaN, signed-zero infinities, ignored-extra evaluation and dependencies, live-null/parameter deferral, shadowing and rollback retain Math's existing behavior. It adds no syntax or callable method reference. See the contract and reactive direction example.

Thin arrows and lambdas spell the same callbacks

limit = 2
items = [1, 2, 3]
over = items.filter(item -> item > limit)
under = items.filter(lambda item: item <= limit)
assert(over, [3])
assert(under, [1, 2])
limit = 1
assert(over, [2, 3])
assert(under, [1])
  • NUC 57 accepts -> wherever => is accepted, including named def headers, and Python-style lambda first, second: expression as exactly (first, second) => (expression). => stays canonical, and lambda is still an ordinary name wherever accepted syntax owns its colon.

Conditional expressions

Conditional expressions pick live values in either spelling

price = 120
shipping = 0 if price > 100 else 5
label = price > 100 ? "free shipping" : "standard"
assert(shipping, 0)
assert(label, "free shipping")
price = 40
assert(shipping, 5)
assert(label, "standard")
  • NUC 59 adds condition ? consequent : alternate and Python's consequent if condition else alternate. Both build one expression that evaluates its condition once and then only the selected branch, so a falsy consequent such as 0 works where price > 100 && 0 || 5 would always give 5.
  • The declarations stay live and switch branches when the condition changes. if statements, including one written after an expression on the same line, keep their accepted readings. See the root contract.

Nullish coalescing

Nullish coalescing defaults unknown values and keeps zero

price = null
shown = price ?? "TBD"
discount = 0
rate = discount ?? 10
assert(shown, "TBD")
assert(rate, 0)
price = 12
assert(shown, 12)
  • NUC 60 adds left ?? right, which yields left unless it is null and otherwise its default. price ?? "TBD" stays live, and discount ?? 10 keeps a real 0 where discount || 10 would give 10.
  • A null read inside left no longer settles the declaration to null, as it does for price || "TBD", so ?? is the expression-level way to default an unknown value. See the root contract.

Instance list ranges and list queries

Instance lists slice like lists and answer live queries

class Task(title):
    this.title = title
plan = Task("plan")
build = Task("build")
recent = Task[-1:]
newest = Task.at(-1).title
assert(recent, [build])
ship = Task("ship")
assert(recent, [ship])
assert(newest, "ship")
assert(Task.list(), [plan, build, ship])
assert(Task.findLastIndex(task => task.title.length == 4), 2)
  • NUC 61 lets a class's instance list take list ranges, such as Task[-1:] and Task[:], and lists every instance with Task.list() or Task.all(). Each stays live as instances are created.
  • Lists and instance lists gain JavaScript's at, lastIndexOf, findIndex, findLast and findLastIndex beside the existing includes, indexOf, concat and slice. All of them are read-only queries that follow the list, the population and what their callbacks read. See the root contract.

Quantifiers

Quantifiers keep claims about every instance live

class Task(skill):
    this.skill = skill
class Worker(skill):
    this.skill = skill
Task("rust")
Task("docs")
Worker("rust")
covered = all t in Task: any w in Worker: w.skill == t.skill
assert(covered, false)
Worker("docs")
assert(covered, true)
  • NUC 62 adds all NAME in COLLECTION: CONDITION and any NAME in COLLECTION: CONDITION. They quantify over class instances, lists and reasoning selections, and they nest, because a condition reads the binders around it.
  • covered is a live declaration: it re-evaluates when a task or a worker is created or a skill changes. Everywhere else all and any stay ordinary names. See the root contract.

Exponentiation

Exponentiation keeps compound growth live

rate = 0.5
years = 2
balance = 100 * (1 + rate) ** years
assert(balance, 225)
years = 3
assert(balance, 337.5)
rate = 1
assert(balance, 800)
  • NUC 64 adds base ** exponent and its compound assignment target **= exponent. a ** b gives exactly Math.pow(a, b), binds tighter than * and groups to the right, so 2 ** 3 ** 2 is 512.
  • balance is a live declaration: it re-evaluates when rate or years changes. As in Python, -x ** 2 means -(x ** 2); write (-x) ** 2 to raise a negative base. See the root contract.

Hypotheticals

Suppose blocks answer what-if questions without changing the model

price = 80
shipping = price >= 100 ? 0 : 9
total = price + shipping
suppose price = 100: assert: shipping, 0
upgrade = suppose price = 120: total
assert: upgrade, 120
assert: total, 89
price = 90
assert: upgrade, 120
assert: total, 99
  • NUC 65 adds suppose blocks. suppose PREMISE: runs the premise and its suite in a hypothetical world, where rules propagate as usual, and then discards everything they did. TARGET = suppose ... keeps the value of the block's last expression as a snapshot, so upgrade stays 120 when price changes later.
  • A thrown error, a return or a normal end all roll the block back, and values leave it as plain data. Everywhere else suppose stays an ordinary name. See the root contract.

Value assignment

Value assignments take an action once while declarations stay live

price = 4
quantity = 3
quote = price * quantity
paid := price * quantity
price = 5
quantity = 2
assert: quote, 10
assert: paid, 12
assert: (why paid)[0].rule, "paid = 12"
  • NUC 66 adds the value assignment TARGET := EXPR, as in Go and Pascal. It evaluates the expression once, records no dependency, and stores the result as a stated fact, exactly as if the value had been written as a literal with =.
  • quote is a declaration, a law that follows price and quantity. paid is an action taken at one moment, so it keeps 12, and why shows the stated literal. See the root contract.

List and population sums

Sums keep totals live as instances change

class Item(price, qty):
    this.price = price
    this.qty = qty
pen = Item(2, 10)
ink = Item(30, 2)
total = Item.sum(item -> item.price * item.qty)
units = Item.sum(item -> item.qty)
assert(total, 80)
assert(units, 12)
ink.qty = 3
Item(5, 1)
assert(total, 115)
assert(units, 14)
  • NUC 68 adds sum() and sum(selector) to lists and class instance lists. Item.sum(item -> item.price * item.qty) totals every instance, adding left to right from 0, and an empty list or class totals 0.
  • total and units are live declarations: they re-evaluate when an instance is created or a quantity changes. Summed values convert to numbers as in arithmetic, so true counts 1 and null counts 0. See the root contract.

List and population extremes

Extremes keep the highest and lowest bids live as bids change

class Bid(bidder, amount):
    this.bidder = bidder
    this.amount = amount
high = Bid.max(bid -> bid.amount)
low = Bid.min(bid -> bid.amount)
assert(high, Number.NEGATIVE_INFINITY)
assert(low, Number.POSITIVE_INFINITY)
ada = Bid("ada", 120)
Bid("bo", 90)
assert(high, 120)
assert(low, 90)
ada.amount = 60
Bid("cy", 150)
assert(high, 150)
assert(low, 60)
  • NUC 69 adds max(), min() and their selector forms to lists and class instance lists. Bid.max(bid -> bid.amount) is the largest amount over every instance, compared exactly as Math.max compares, and before the first bid high is -Infinity and low is Infinity, the values of Math.max() and Math.min().
  • high and low are live declarations: they re-evaluate when an instance is created or an amount changes. Compared values convert to numbers as in arithmetic, so the result is always a number. See the root contract.

List and population distinct values

Distinct values keep the regions of orders live as orders change

class Order(customer, region):
    this.customer = customer
    this.region = region
regions = Order.distinct(order -> order.region)
buyers = Order.distinct(order -> order.customer).length
assert(regions, [])
first = Order("ada", "north")
Order("bo", "south")
Order("ada", "north")
assert(regions, ["north", "south"])
assert(buyers, 2)
first.region = "east"
Order("cy", "south")
assert(regions, ["east", "south", "north"])
assert(buyers, 3)
  • NUC 71 adds distinct() and distinct(selector) to lists and class instance lists. Order.distinct(order -> order.region) lists each region once, in the order of the first order that has it, and an empty list or class gives [].
  • regions and buyers are live declarations: they re-evaluate when an order is created or its region changes. Values are compared with the deep equality of includes and never converted, so 1 and "1" stay apart. See the root contract.

Object entry loops

Entry loops read the keys and values of a record

class Item(shelf, name, qty):
    this.shelf = shelf
    this.name = name
    this.qty = qty
def stock(shelf, table):
    for name, qty of table: Item(shelf, name, qty)
    return Item.length
assert([stock("pantry", {rice: 4, oil: 2, salt: 1}), stock("fridge", {milk: 1, eggs: 12})], [3, 5])
assert(Item.map(item -> item.shelf + ":" + item.name + item.qty).join(" "), "pantry:rice4 pantry:oil2 pantry:salt1 fridge:milk1 fridge:eggs12")
  • NUC 72 adds for KEY, VALUE of SOURCE:, also spelled with in. It visits an object's own entries in insertion order, binds each key and its value, and runs the body once per entry, as a single-name loop runs once per item.
  • stock takes any table: the pantry and fridge tables have different keys, and the loop reads each one without naming them. A rolled-back try or a suppose block leaves every object's entries where they were, in the same order. Any other source raises TypeError("<source> is not an object"). See the root contract.

While loops

While loops step a world model until a goal holds

class Tank(level):
    this.level = level
tank = Tank(2)
full = tank.level >= 10
hours = 0
while not full:
    tank.level = tank.level + 3
    hours = hours + 1
assert(tank.level, 11)
assert(hours, 3)
assert(full, true)
  • NUC 63 adds while CONDITION: with an indented or same-line body. The condition is tested with the truthiness of if before every iteration, and each iteration sees the live values that its own changes caused, such as full here.
  • A loop runs once, imperatively, like for: it leaves no standing rule and does not run again when tank.level changes later. A runaway loop stops after 100,000 iterations with TypeError("Loop did not terminate") and rolls back. while stays an ordinary name everywhere else. See the root contract.

Destructuring

Destructuring declares one live name per field or position

size = {width: 3, height: 2}
{width, height} = size
area = width * height
corner = [4, 1]
x, y = corner
size.width = 5
corner = [0, 7]
assert: [width, height, area], [5, 2, 10]
assert: [x, y], [0, 7]
  • NUC 67 adds destructuring declarations: object patterns such as {width, height} = size, renamed targets such as {width: wide} = size, and Python-style unpacking such as x, y = corner.
  • Each name is an ordinary declaration of its own, width = size.width and x = corner[0], so every name stays live: changing size or corner updates it. See the root contract.

Predicate counts

Predicate counts keep the number of open tickets live as tickets change

class Ticket(open): this.open = open
pending = Ticket.count(ticket -> ticket.open)
first = Ticket(true)
second = Ticket(false)
assert: pending, 1
second.open = true
assert: pending, 2
first.open = false
assert: pending, 1
  • NUC 73 adds count(predicate) to lists and class populations. It returns filter(predicate).length without allocating the filtered result, and calls the predicate once per item in receiver order.
  • Counts remain live in declarations and class-level rules. A valid empty receiver gives 0; a missing or non-function predicate raises TypeError("count expects a function"). The accepted filter spelling remains unchanged. See the root contract.

Final rest parameters

Final rest parameters collect positional extras without changing fixed defaults

def collect(head = 1, ...tail) => [head, tail]
assert(collect(), [1, []])
assert(collect(2, 3, 4), [2, [3, 4]])
assert(collect(5, [6, 7]), [5, [[6, 7]]])
  • A final ...name in a parenthesized user signature receives a fresh list, including [] when there are no extras. Functions, methods and constructors share binding; a constructor label may fill a fixed slot, not its rest slot. No bare rest form or call-site spread is introduced. See NUC 74 and the root contract.

List and population modes

Modes keep the most requested size live as orders change

class Order(customer, size):
    this.customer = customer
    this.size = size
popular = Order.mode(order -> order.size)
assert(popular, null)
first = Order("ada", "M")
Order("bo", "L")
Order("cy", "L")
assert(popular, "L")
first.size = "S"
Order("di", "S")
Order("ed", "S")
assert(popular, "S")
  • NUC 75 adds mode() and mode(selector) to lists and class instance lists. Order.mode(order -> order.size) is the size that the most orders have; on a tie, the size that appears first wins, and an empty list or class gives undefined, stored as null.
  • popular is a live declaration: it re-evaluates when an order is created or its size changes. Items are matched with the deep equality of includes and never converted, as distinct matches them, so 1 and "1" are counted apart. See the root contract.

Noun list queries

Noun queries read like the model they describe

class Order(customer, amount):
    this.customer = customer
    this.amount = amount
revenue = sum of Order.map(order -> order.amount)
largest = max of Order.map(order -> order.amount)
regular = mode of Order.map(order -> order.customer)
Order("ada", 20)
second = Order("bo", 45)
Order("ada", 15)
assert: [revenue, largest, regular], [80, 45, "ada"]
second.amount = 70
Order("cy", 60)
assert: [revenue, largest, regular], [165, 70, "ada"]
amounts = [45, 20, 15]
amounts.sort()
assert: [amounts, min of amounts], [[15, 20, 45], 15]
  • Revision 2 of NUC 75 adds NOUN of OPERAND for the list queries named by nouns: sum, max, min and mode. mode of list means exactly list.mode(), and declarations that use it stay live the same way.
  • Actions keep the method call: amounts.sort() sorts in place, while min of amounts reads a value. A selector stays a method argument, as in Order.max(order -> order.amount), which equals max of Order.map(order -> order.amount). See the root contract.

List and population extreme items

Extreme items keep the leading and lowest bids live as bids change

class Bid(bidder, amount):
    this.bidder = bidder
    this.amount = amount
leader = Bid.maxBy(bid -> bid.amount)
lowest = Bid.minBy(bid -> bid.amount)
assert(leader, null)
assert(lowest, null)
ada = Bid("ada", 120)
Bid("bo", 90)
Bid("cy", 120)
Bid("di", 90)
assert(leader.bidder, "ada")
assert(lowest.bidder, "bo")
ada.amount = 60
Bid("ed", 150)
assert(leader.bidder, "ed")
assert(lowest.bidder, "ada")
  • NUC 77 adds maxBy(selector) and minBy(selector) to lists and class instance lists. Bid.maxBy(bid -> bid.amount) is the bid with the largest amount, the instance itself, so leader.bidder names the bidder. Before the first bid leader and lowest are null, and on a tie the earlier bid wins: cy and di do not displace ada and bo.
  • leader and lowest are live declarations: they re-evaluate when a bid is created or an amount changes. Keys compare as > and < compare them, so numbers compare as numbers, strings as text and dates by time. See the root contract.

Extends clauses

Extends clauses declare subclasses as JavaScript and Java do

class Sensor(name, reading):
    this.name = name
    this.reading = reading
$Sensor.alarm = $Sensor.reading > 50
class Thermometer extends Sensor
class Barometer(name, reading, unit) extends Sensor:
    this.name = name
    this.reading = reading
    this.unit = unit
kitchen = Thermometer("kitchen", 21)
roof = Barometer("roof", 1013, "hPa")
assert(kitchen.alarm, false)
assert(roof.alarm, true)
assert(roof.unit, "hPa")
kitchen.reading = 64
assert(kitchen.alarm, true)
assert(Sensor.length, 0)
  • NUC 78 accepts extends in a class header as an optional spelling of the colon parent. class Thermometer extends Sensor is the bodyless subtype that class Thermometer: Sensor with an indented pass declares, and class Barometer(name, reading, unit) extends Sensor: takes the indented body that class Barometer(name, reading, unit): Sensor takes.
  • $Sensor.alarm applies to both subtypes, which use Sensor's constructor or their own, and each subtype keeps its own instance list. extends stays an ordinary name everywhere else. See the root contract.

Property selector arguments

Selector arguments read properties without naming a parameter

class Order(customer, amount, paid):
    this.customer = customer
    this.amount = amount
    this.paid = paid
revenue = Order.sum(.amount)
largest = Order.maxBy(.amount)
settled = Order.filter(.paid).map(.customer)
Order("ada", 20, true)
bo = Order("bo", 45, false)
assert: [revenue, largest.customer, settled], [65, "bo", ["ada"]]
bo.paid = true
Order("cy", 60, false)
assert: [revenue, largest.customer, settled], [125, "cy", ["ada", "bo"]]
  • NUC 79 lets a call argument written .amount, or a dotted path such as .address.city, stand for the function order -> order.amount that reads it. Order.sum(.amount) totals every order, and Order.filter(.paid).map(.customer) names the customers who have paid.
  • A selector stays live like the lambda it abbreviates: a new order or a changed amount re-evaluates revenue and largest. It is written only as a whole call argument, so x = .amount stays a syntax error. See the root contract.

List and population averages

Averages keep mean temperatures live as readings change

class Room(name, temps):
    this.name = name
    this.temps = temps
$Room.mean = $Room.temps.average()
lab = Room("lab", [21, 23])
hall = Room("hall", [17, 18])
building = Room.average(room -> room.mean)
assert: [lab.mean, hall.mean, building], [22, 17.5, 19.75]
hall.temps.push(25)
Room("attic", [12])
assert: [hall.mean, building], [20, 18]
  • NUC 80 adds average() and average(selector) to lists and class instance lists. $Room.temps.average() is the mean of each room's readings, and Room.average(room -> room.mean) the mean over every room: exactly sum(...) divided by the number of items.
  • $Room.mean and building are live declarations: a new reading or a new room re-evaluates them. An empty list or class averages to NaN, as 0 / 0 does. See the root contract.

Object spread entries

Spread entries keep derived listings in step with their products

class Product(name, price):
    this.name = name
    this.price = price
$Product.listing = {...$Product, currency: "USD"}
lamp = Product("lamp", 40)
draft = {...lamp.listing, price: lamp.price * 0.75}
assert: [lamp.listing.price, draft.price], [40, 30]
lamp.price = 60
lamp.stock = 3
assert: [lamp.listing, draft], [{id: "lamp", name: "lamp", price: 60, stock: 3, currency: "USD"}, {id: "lamp", name: "lamp", price: 45, stock: 3, currency: "USD"}]
  • NUC 81 adds spread entries to object literals: ...SOURCE copies the entries of the object SOURCE holds, in order, and a later entry for the same key replaces its value. $Product.listing copies each product and adds a currency, and draft copies the listing with a discounted price.
  • A spread stays live: a changed price and the new stock entry reach both records. A class-level rule that spreads its own receiver leaves out the property it declares, so listing never holds an older listing. See the root contract.

Depth-limited reasoning selections

Depth keeps an alarm's explanation to its direct causes

pressure = 120
limit = 100
temperature = 70
manual = false
strained = pressure > limit
hot = temperature > 90
alarm = [strained, hot, manual].includes(true)
causes = alarm |> why |> depth(1)
assert: causes.map(step -> step.node), ["alarm", "strained", "hot", "manual"]
assert: (why alarm).length, 7
pressure = 90
temperature = 95
assert: causes.map(step -> step.holds), [true, false, true, false]
  • NUC 82 adds the reasoning stage |> depth(LIMIT). It keeps the steps of a selection that lie within LIMIT steps of where the walk that selected them began, in their order, so alarm |> why |> depth(1) is the alarm and the facts it reads directly: four steps, where why alarm selects seven.
  • The selection stays live: new pressures and temperatures update the steps it keeps, and a limit read from a variable re-derives it when the variable changes. See the root contract.

Hypothetical differences

Diff lists what a warmer room would change in a heating plan

temperature = 18
target = 21
heating = temperature < target
fan = temperature > 30
cost = heating ? 4 : 0
suppose temperature = 24:
    changes = model |> diff
    assert: changes.map(step -> step.node), ["temperature", "heating", "cost"]
    target = 26
    assert: changes.map(step -> step.node), ["temperature", "target"]
assert: [heating, cost], [true, 4]
  • NUC 83 adds the reasoning stage |> diff. Inside a suppose block it keeps the steps of a selection that the block added or changed, so model |> diff answers what a hypothetical would do without naming any node: warming the room to 24 turns the heating off and drops its cost, while the fan, which also reads the temperature, keeps its value.
  • The selection stays live inside the block: with the target raised to 26 the heating is back on, so only the temperature and the target still differ. Outside a block the stage raises TypeError("Cannot use 'diff' outside suppose"). See the root contract.

List and population medians

Medians keep typical delivery times live as orders arrive

class Order(city, minutes):
    this.city = city
    this.minutes = minutes
Order("oslo", 30)
Order("oslo", 25)
Order("bergen", 90)
late = Order("bergen", 105)
typical = Order.median(order -> order.minutes)
local = Order.filter(order -> order.city == "oslo").median(order -> order.minutes)
assert: [typical, local], [60, 27.5]
Order("oslo", 8)
late.minutes = 40
assert: [typical, local], [30, 25]
  • NUC 84 adds median() and median(selector) to lists and class instance lists. Order.median(order -> order.minutes) is the middle delivery time of every order, and with an even number of orders the mean of the two middle times.
  • typical and local are live declarations: a new order or a changed time re-evaluates them. Once the late order takes 40 minutes, the 90-minute order is the slowest, so however slow it is, typical stays 30, while the mean of the five orders is 38.6. See the root contract.

Is comparisons

Is matches quoted and bare words as == does

status = "PENDING"
if status is SHIPPED:
    stage = "done"
else:
    stage = "open"
late = (status is not 'SHIPPED')
assert: [stage, late], ["open", true]
status = "SHIPPED"
assert: [stage, late], ["done", false]
SHIPPED = "DELIVERED"
assert: [stage, late], ["open", false]
  • NUC 86 adds is and is not as spellings of == and != for numbers, strings, booleans, null and property reads. A quoted right operand is a string in either quote, and a lone bare word such as SHIPPED reads the variable of that name, or its own text while nothing defines it. Defining, changing or deleting SHIPPED re-evaluates every rule that compares with it, while the quoted 'SHIPPED' stays text.
  • is works in conditions, parentheses, call arguments, lists and object values. Where a statement can follow, it stays an ordinary name, so write late = (status is not 'SHIPPED') rather than leaving the comparison bare. See the root contract.

Initializing constructor parameters

Constructor parameters can assign the properties they name

class Room(this.name, this.temp, this.target = 21)
$Room.heating = $Room.temp < $Room.target
lab = Room("lab", 18)
hall = Room("hall", 23, 25)
assert: [lab.heating, hall.heating], [true, true]
lab.temp = 22
hall.target = 20
assert: [lab.heating, hall.heating], [false, false]
  • NUC 87 lets a constructor parameter written this.name or self.name also assign the property it names, so class Room(this.name, this.temp, this.target = 21) declares what class Room(name, temp, target = 21): declares with one indented this.name = name line per parameter. Types, literal defaults and labelled arguments work as before.
  • The assignments run before the constructor's own statements, and the properties they make are ordinary: $Room.heating follows a changed temperature or target. Parameters without the prefix still create no properties. See the root contract.

Derivation path selections

Path shows how a discount tier reaches an order total

subtotal = 120
tier = "gold"
rate = tier == "gold" ? 20 : 5
discount = subtotal * rate / 100
shipping = 9
total = subtotal - discount + shipping
how = total |> why |> path(tier)
assert: how.map(step -> step.node), ["total", "discount", "rate", "tier"]
assert: (why total).length, 6
tier = "silver"
assert: how.map(step -> step.holds), [123, 6, 5, "silver"]
  • NUC 88 adds the reasoning stage |> path(TARGET). It keeps the steps of a selection that lie on a dependency path between the walk and TARGET, in their order, so total |> why |> path(tier) is how the tier reaches the total: through the discount and its rate, four steps where why total selects six, leaving out the subtotal and the shipping.
  • The selection stays live: once the tier is silver, the same four steps hold the new rate, discount and total, and a rule rewired to read the tier would join it. After affects, |> path(TARGET) keeps what a change moves on its way to TARGET. See the root contract.

Named function declarations

Function declarations keep JavaScript-style functions live

rate = 5
function shipping(weight) { return rate * weight }
function total(price, weight) {
    return price + shipping(weight)
}
order = total(40, 2)
assert(order, 50)
rate = 4
assert(order, 48)
function shipping(weight) { return rate * weight + 1 }
assert(order, 49)
  • NUC 89 accepts the named function declaration of JavaScript, TypeScript and PHP. function total(price, weight) { ... } declares exactly the function that def total(price, weight): declares with the same statements, written in braces instead of an indented body.
  • order stays live through both functions: a new rate updates it, and so does redeclaring shipping, whichever spelling either declaration uses. Declarations are not hoisted, and in an expression function still takes only an anonymous literal. See the root contract.

Enum declarations

Enums name a model's categories once

enum Stage: TODO, DOING, DONE
class Task(title, stage):
    this.title = title
    this.stage = stage
$Task.active = $Task.stage == Stage.DOING
write = Task("write", Stage.TODO)
review = Task("review", Stage.DOING)
assert([write.active, review.active], [false, true])
write.stage = Stage.DOING
assert(Task.count(t -> t.active), 2)
  • NUC 90 adds enum declarations. enum Stage: TODO, DOING, DONE declares Stage as an object whose members hold their own names, so Stage.DOING is "DOING". A member may instead take a literal value, as in enum Level: LOW = 1, HIGH = 3, and enum Stage: followed by an indented block lists one member per line.
  • Rules that read a member stay live like any other: moving write to Stage.DOING makes it active, and the count follows. The enum itself is read-only, so Stage.DONE = "X" raises TypeError("Enums are read-only"). See the root contract.

List comprehensions

List comprehensions keep Python-style lists live

class Item(name, price):
    this.name = name
    this.price = price
budget = 10
cheap = [item.name for item in Item if item.price <= budget]
Item("pen", 3)
ink = Item("ink", 12)
assert(cheap, ["pen"])
budget = 15
assert(cheap, ["pen", "ink"])
ink.price = 20
assert(cheap, ["pen"])
  • NUC 92 accepts Python's list comprehensions. [item.name for item in Item if item.price <= budget] is exactly Item.filter(item => item.price <= budget).map(item => item.name), and for item of Item is the same as for item in Item.
  • cheap stays live like the arrow spelling: a new budget or a changed price updates it. A comprehension has one for and at most one if, and its lambdas read variables and their own name but not the parameters of an enclosing function. See the root contract.

Implicit receiver properties

A leading dot reads a property of the assigned object

class Box(w, h):
    this.w = w
    this.h = h
$Box.area = .w * .h
box = Box(2, 3)
box.label = `${.w}x${.h}`
assert([box.area, box.label], [6, "2x3"])
box.w = 5
box.h = 4
assert([box.area, box.label], [20, "5x4"])
  • NUC 102 reads .w in the value of an assignment to a property as a property of the same object, its receiver: $Box.area = .w * .h is $Box.area = $Box.w * $Box.h, and box.label = `${.w}x${.h}` reads box.w and box.h.
  • The parser writes the path out, so the rule renders, depends, stays live and rolls back exactly as its spelled-out form. A call argument that starts with . stays a selector, and a leading dot anywhere without a property target keeps its error. See the root contract.

Any and all class references

Any and all state a class condition and the rule it guards

class Object(number):
    this.number = number
every Object.label is square
if any Object.number > 4:
    all Object.color is red
a = Object(3)
b = Object(7)
assert([a.label, b.label], ["square", "square"])
assert([a.color, b.color], [null, "red"])
  • NUC 104 reads any Object and all Object as the class reference $Object, wherever NUC 98 reads every Object as one and inside if statements, where NUC 98 rejects every Object. So if any Object.number > 4: is the class-level conditional if $Object.number > 4:, and all Object.color is red is the rule $Object.color is red it guards for each Object over 4.
  • They are spellings, not quantifiers: each instance is tested on its own, so only b turns red. any n in xs: n > 4 and all n in xs: n > 4 stay the quantifiers of NUC 62, and variables named all and any work as before. See the root contract.

Reactive list flattening

Flat turns nested lists into one live list

weeks = [["mon", "tue"], ["wed"]]
days = weeks.flat()
assert(days, ["mon", "tue", "wed"])
weeks.push(["thu", "fri"])
assert(days, ["mon", "tue", "wed", "thu", "fri"])
  • NUC 103 adds JavaScript's flat to lists and class populations: weeks.flat() is exactly weeks.flatMap(v => v), and weeks.flat(2) flattens two levels, as two such steps do. flat(0) copies, and flat(Number.POSITIVE_INFINITY) flattens every level.
  • days stays live like its flatMap spelling, so another week of days extends it. See the root contract.

Membership tests

Membership tests read in the order they are said

allowed = ["new", "paid"]
status = "paid"
open = status in allowed
closed = status not in allowed
assert([open, closed], [true, false])
allowed = ["new"]
assert([open, closed], [false, true])
  • NUC 97 adds item in collection and item not in collection, the shorter spellings of collection.includes(item) and !collection.includes(item) for lists, classes and strings. Both stay live, so changing allowed re-evaluates open and closed.
  • A declaration over a null collection holds null until the collection is set, and any other value, such as a number or an object, raises TypeError("<collection> is not a collection"). in keeps its for, comprehension and quantifier readings. See the root contract.

Every and predicate sentences

Every states a class-level claim as it is said

class Human(name: str):
    this.name = name
every Human is mortal
every Human.legs is 2
socrates = Human("Socrates")
assert([socrates.mortal, socrates.legs], [true, 2])
assert(every h in Human: h.mortal, true)
  • NUC 98 reads every Human as the class reference $Human, so every Human.legs is 2 is the class-level rule $Human.legs = 2, and every h in Human: h.mortal as the quantifier all h in Human: h.mortal.
  • Inside an if statement every Human raises SyntaxError("Use all instead of every inside if") before the program runs, so a condition reads if any Human.age > 60: and the rule it guards all Human.senior = true.
  • The statement every Human is mortal, like $Human is mortal, states $Human.mortal = true for every instance, and why reports it as that rule. Elsewhere every stays an ordinary name, so xs.every(...) and a variable named every work as before. See the root contract.

Benchmark

This is the comparation our sample order app in Nucleoid IDE against MySQL and Postgres with using Express.js and Sequelize libraries.

Benchmark

Performance benchmark happened in t2.micro of AWS EC2 instance and both databases had dedicated servers with no indexes and default configurations.

https://github.com/NucleoidAI/benchmark

As seen in the chart, for applications with average complexity, Nucleoid's performance is close to linear because of on-chain data store, in-memory computing model as well as limiting the IO process.



⭐️ Star us on GitHub for the support

World Models are an emerging field and thanks to declarative logic programming, we have a brand-new approach to building World Models. Join us in shaping the future of AI!

Nobel


Contributors

User NucBot
NucBot
User canmingir
canmingir
User 322332
322332
User dependabot[bot]
dependabot[bot]
User francisco-giancarelli-crombie
francisco-giancarelli-crombie
User Gulshanaggarwal
Gulshanaggarwal
User CanPacis
CanPacis
User durulkoca
durulkoca
User halilcengel
halilcengel
User EnesKeremAYDIN
EnesKeremAYDIN
User russle-smith
russle-smith
User russellgray
russellgray

Generated by NucBot

About

World Model πŸŒ±πŸ‹πŸŒ

nucleoid.ai

Topics

declarative-programmingknowledge-baseknowledge-graphlogic-programmingneuro-symbolicneuro-symbolic-aineurosymbolic-aiprogramming-languagereasoningreasoning-enginesymbolic-world-modelsworld-modelworld-models

Resources

Readme
Apache-2.0 license

Code of conduct

Code of conduct

Contributing

Contributing
Activity
Custom properties

Stars

770 stars

Watchers

14 watching

Forks

34 forks
Report repository

Releases

Used by

Contributors

Languages

You are visitor 01402503 β€” GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800Γ—600. No cookies required.