Primitive classes¶
In Sutra, there is really only one primitive. Everything is a vector in the extended-state layout โ a flat array of coordinates where some coordinates carry semantic content from the embedding model and some carry computational/symbolic state.
Everything else you think of as a "type" โ int, float, complex, bool, fuzzy, trit, char โ is what we call a primitive class: a compile-time tag that tells the compiler which coordinates of the vector carry meaning for this value and what rules apply when you operate on it. The runtime data is the same shape no matter which class you pick; the class names just tell you (and the compiler) what part of that shape you actually care about.
This is why Sutra's type system can feel simpler and weirder than most โ there's no "primitive vs. object" distinction at the bottom of the hierarchy. It's vectors all the way down.
The class hierarchy¶
graph TD
V[vector<br/>the one true primitive]
V --> NUM[numeric family<br/>real + imag axes]
V --> TRU[truth family<br/>truth axis]
V --> SEM[semantic vectors<br/>full semantic block]
NUM --> INT[int]
NUM --> FLT[float]
NUM --> CPX[complex]
TRU --> BOOL[bool]
TRU --> FUZZ[fuzzy]
TRU --> TRIT[trit]
SEM --> BV[basis_vector / embed]
SEM --> ROLE[role matrices]
classDef root fill:#512da8,color:#fff,stroke:#311b92
classDef family fill:#7e57c2,color:#fff,stroke:#512da8
classDef leaf fill:#d1c4e9,color:#311b92,stroke:#512da8
class V root
class NUM,TRU,SEM family
class INT,FLT,CPX,BOOL,FUZZ,TRIT,BV,ROLE leaf
The left two branches are what this page is about. The semantic branch is covered in the hello-sutra tutorial and the bind/unbind work.
The numeric family¶
int, float, and complex all put their value on the same pair of synthetic axes โ AXIS_REAL and AXIS_IMAG. They're the same data. The only difference is the compile-time tag.
graph LR
subgraph RT[runtime vector โ synthetic block]
direction LR
REAL["[0] REAL"]
IMAG["[1] IMAG"]
TRU["[2] TRUTH"]
DOT["โฆ"]
end
I[int 5] -->|writes| REAL
F[float 3.14] -->|writes| REAL
C1[complex 5+3i] -->|writes re| REAL
C1 -->|writes im| IMAG
classDef src fill:#7e57c2,color:#fff,stroke:#512da8
classDef ax fill:#d1c4e9,color:#311b92,stroke:#512da8
class I,F,C1 src
class REAL,IMAG,TRU,DOT ax
| Class | Real axis | Imag axis | Compile-time tag |
|---|---|---|---|
int |
value | 0 |
whole numbers only; reject fractional literals |
float |
value | 0 |
fractional literals OK |
complex |
real part | imaginary part | both axes populated |
Every number is on the complex plane. An int is just a complex number with an imaginary part of zero, tagged so the programmer can't accidentally store a fractional or imaginary part. This is why the 5i imaginary literal needs no separate primitive class โ the axes are already there; the suffix just tells the compiler which axis to write to.
The truth family¶
bool, fuzzy, and trit all put their value on the same axis โ AXIS_TRUTH. Different compile-time tags say how strictly the value is interpreted and how it polarizes under defuzzification.
graph TD
subgraph TA[truth axis โ synthetic[2]]
direction LR
N[-1.0<br/>false]
Z[0.0<br/>unknown]
P[+1.0<br/>true]
N --- Z
Z --- P
end
BOOL[bool]
FUZZ[fuzzy]
TRIT[trit]
BOOL -->|poles only| N
BOOL -->|poles only| P
FUZZ -->|any value| N
FUZZ -->|any value| Z
FUZZ -->|any value| P
TRIT -->|any value| N
TRIT -->|any value| Z
TRIT -->|any value| P
classDef ax fill:#d1c4e9,color:#311b92,stroke:#512da8
classDef src fill:#7e57c2,color:#fff,stroke:#512da8
class N,Z,P ax
class BOOL,FUZZ,TRIT src
boolโ interpreted strictly at-1(false) or+1(true). The compile-time tag says "I expect one of the two poles."fuzzyโ any value in[-1, +1]. Defuzzification polarizes toward one of the two binary poles.tritโ any value in[-1, +1], with0as a first-class attractor.defuzzify_tritpolarizes toward{-1, 0, +1}โ the neutral point is preserved instead of collapsing. The "trinary digit" sibling ofbit.
Defuzzification behavior side-by-side¶
graph LR
IN[truth-axis value x]
IN --> DF[defuzzify<br/>binary]
IN --> DT[defuzzify_trit<br/>three-way]
DF --> OF[x โ -1 or +1<br/>midpoint collapses]
DT --> OT[x โ -1, 0, or +1<br/>neutral preserved]
classDef op fill:#7e57c2,color:#fff,stroke:#512da8
classDef out fill:#d1c4e9,color:#311b92,stroke:#512da8
classDef in1 fill:#ede7f6,color:#311b92,stroke:#7e57c2
class IN in1
class DF,DT op
class OF,OT out
Neutralness โ the third thing¶
Most languages have "truthiness" (values that read as true โ nonzero ints, nonempty strings) and "falsiness" (values that read as false โ zero, null, empty). Both collapse into a two-way split.
Sutra has a third: neutralness.
A truth-axis value at 0.0 isn't "falsy" โ it's explicitly not taking a side. Writing unknown (short form unk) in your code says "this value is on the truth axis, at the neutral point, not because we don't know what it is but because it is the neutral."
graph LR
TR[truthy<br/>+1]:::pos
FA[falsy<br/>-1]:::neg
NE[neutral<br/>0<br/><b>unknown</b>]:::neu
FA --- NE
NE --- TR
classDef pos fill:#7e57c2,color:#fff,stroke:#512da8
classDef neg fill:#7e57c2,color:#fff,stroke:#512da8
classDef neu fill:#ffb74d,color:#311b92,stroke:#f57c00
Neutralness is the semantic distinction the trit primitive class exists to honor. When you defuzzify a trit near zero, it stays at zero. Defuzzify the same value as a fuzzy, and it gets pulled to one of the poles.
"Not initialized" is not a thing¶
In most languages, a variable declared without a value is in an uninitialized limbo โ reading it is a bug (or a null, or a segfault). In Sutra, there is no uninitialized state. Every primitive class has a natural neutral, and a variable that has been declared but not assigned already has that neutral as its value.
graph TD
D[variable declared<br/>no assignment yet]
D -->|class is fuzzy/trit| N1[value = 0 on truth axis<br/>'unknown']
D -->|class is int/float/complex| N2[value = 0 + 0i<br/>origin]
D -->|class is vector| N3[value = zero vector]
classDef root fill:#512da8,color:#fff,stroke:#311b92
classDef leaf fill:#d1c4e9,color:#311b92,stroke:#512da8
class D root
class N1,N2,N3 leaf
There is no null. The neutral is real, not a sentinel. When you read a not-yet-assigned variable, you get the neutral, and the neutral is a meaningful value in the math that follows.
Functional completeness and factorable logic¶
The three primitives !, &&, || are in Lagrange polynomial form
on the three-valued grid {-1, 0, +1}:
!a = -a
a && b = (a + b + ab โ aยฒ โ bยฒ + aยฒbยฒ) / 2
a || b = (a + b โ ab + aยฒ + bยฒ โ aยฒbยฒ) / 2
These three are functionally complete โ every other logical connective can be expressed as a composition. And because everything is polynomial arithmetic, compositions symbolically collapse to simpler polynomials. Two standard cases reduce surprisingly far:
On a signed truth scale where -1 = false, 0 = unknown, +1 = true,
"the two values disagree" is exactly the negative product of their
signs, and "they agree" is the positive product. Four levels of
primitive composition collapse to a single multiplication.
See Logical operations for the polynomial forms of all eight standard connectives and the full derivations.
Consequences of the same-data-different-tag structure¶
Some things that follow from this hierarchy and are worth internalizing:
- Assignment within a family is nearly free at runtime. Assigning an
intvalue to afloatslot does nothing to the storage โ it's already the right shape. The compile-time tag changes; the vector data doesn't. Same forbool โ fuzzyorfuzzy โ trit. - Assignment across families is a real operation. Moving from the truth family to the numeric family (or vice versa) requires reading one axis and writing another, because the value lives in a different part of the vector.
- Operations are defined once per family, not once per primitive class. There's one addition for numeric-family values (complex addition โ which reduces to real addition when the imaginary parts are zero). There's one
and/or/notfor truth-family values (t-norm / t-conorm on the truth axis).
Related reading¶
- Logical operations โ the truth-family algebra.
- Numeric math โ the numeric-family algebra.
- Ontology โ the broader class-tree picture including user-defined classes and embeddings.
- Memory โ how rotation binding lets vectors act as arrays and hashmaps.