Types, Bindings & Value Semantics
Sema is statically typed with local inference. This page covers the base types,
the numeric model (which is stricter than Python’s on purpose), the prelude’s
Option/Result (there is no null), the built-in collections, and how
bindings, mutability, and value semantics work.
Scalars
Section titled “Scalars”| Type | Meaning |
|---|---|
int | Arbitrary-precision integer (the default integer). |
i8 i16 i32 i64 u8 u16 u32 u64 | Sized, fixed-width integers. |
f16 f32 f64 | IEEE floating point (f64 is the default float). |
bool | true / false. |
str | Unicode text; implements the Semantic trait natively. |
bytes | Raw byte string. |
Static type checking happens at sema check time — before the program runs. It
verifies call arity, struct construction (unknown field names), literal-argument
base-type conflicts (add(1, "two") where add wants two ints), return-type
conflicts, and trait-object conformance. The checker is conservative by
design: it only flags a mismatch when it can resolve a concrete type on both
sides, so generative outputs, ports, and untyped locals are left alone (typed
any). It is the linter a type system would give you, with zero false positives.
Numerics — checked, never silent
Section titled “Numerics — checked, never silent”This is one of the places Sema deliberately diverges from Python. Arithmetic on
the default int/float is checked:
- Overflow is a typed
OverflowError, never a silent wrap. - Division and modulo by zero, and math-domain errors, raise
(
DivisionByZero,ValueError) instead of returningNaN/inf. - Conversions are explicit constructor calls; there is no implicit widening between the integer and float families.
def half(x: int) -> int !{}: return x / 2 # DivisionByZero is impossible here; x / 0 would raise
# Mixing int and float requires an explicit conversion — no silent promotion.ratio = f64(hits) / f64(total)Width casts round through the real format
Section titled “Width casts round through the real format”A sized type is observable, not cosmetic — casts round x through the actual
IEEE / two’s-complement representation, which is exactly the quantization behavior
ML code needs, native:
q = f16(0.1) # != 0.1 — rounded through IEEE binary16sat = f8(1000.0) # == 448.0 — FP8-E4M3 saturatesr = bf16(x) # keeps f32's range but only 7 mantissa bits
w = i8(200) # == -56 — two's-complement wrap, like a systems `as iN`b = u8(300) # == 44Float cast forms: f32(x), f16(x), bf16(x), f8(x). Integer cast forms:
i8/i16/i32/u8/u16/u32 are explicit narrowing conversions with wrap;
int/i64/u64 are the full-width forms.
No null — Option[T] and Result[T, E]
Section titled “No null — Option[T] and Result[T, E]”There is no null and no None reference in Sema. Absence and fallibility
are ordinary sum types from the prelude:
Option[T]—Some(x)orNone. Use it when a value may be absent.Result[T, E]—Ok(x)orErr(e). Use it when an operation may fail.
None is the empty variant of a sum type — you consume it with match, the
combinators, or ?/unwrap, never with an identity test (x == None is not how
you check absence — there is no null reference to compare against).
def find_user(id: int) -> Option[User] !{db.read}: rows = db.query("select * from users where id = ?", [id]) return Some(User.from_row(rows[0])) if len(rows) > 0 else None
# Consume it by matching — the only exhaustive way.match find_user(42): case Some(u): greet(u) case None: prompt_signup()Full error handling — ?, unwrap, expect/except, and the Result/Option
combinators — is on the Error Handling page.
Collections — homogeneous and value-semantic
Section titled “Collections — homogeneous and value-semantic”list[T], dict[K, V], and set[T] are built in, homogeneous (one element
type), and value-semantic (see below). Literals and comprehensions are Pythonic:
xs = [1, 2, 3]kept = [x for x in xs if x > 1] # comprehensionby_id = {u.id: u for u in users} # dict comprehensiontags = {t for t in raw if len(t) > 0} # set comprehension
point: tuple[int, int] = (3, 4) # tuple, literal (a, b)x, y = point # destructured by irrefutable pattern- Slices on
list/str/bytesuse Python syntax (xs[1:]) and return copies. dictkeys andsetelements must be hashable with total==.- Heterogeneous collections are excluded (Codon divergence). Dynamic
JSON-shaped data enters through the prelude
JsonValuesum and leaves it at a typed boundary (parse[T]against a struct schema), never via stringly subscripting.
A comprehension is the sequential base case of the parallel [...] form — same
scoping and typing rules.
List methods include append/extend/insert/pop/sort/reverse/index/
count/slice/first/last/contains/join; dict methods include
get/set/keys/values/items/update/pop/setdefault/contains/len;
plus the free builtins enumerate/zip/map/filter/sorted/reversed/
sum/min/max/mean.
String methods
Section titled “String methods”str carries a method surface:
upper lower strip lstrip rstripsplit join replacestartswith endswith containsfind rfind count # find/rfind return a char index or -1slice substringcapitalize titleisdigit isalpha isalnum isspacerepeat lenname = " Ada Lovelace ".strip()slug = name.lower().replace(" ", "-")if slug.startswith("ada") and slug.count("-") == 1: log.info("slug", value=slug)Bindings and mutability
Section titled “Bindings and mutability”Bindings are immutable by default. mut x = … declares a rebindable binding
whose aggregate contents may also be mutated in place. Assigning to a plain
binding is a compile error. Every binding is monomorphic — one type for
its lifetime; rebinding cannot change type.
let base = 10 # immutable (the `let` keyword is optional for bindings)count = 10 # also immutable — a plain binding is not rebindablemut total = 0 # rebindablefor x in xs: total = total + x # ok — `total` is `mut`
# count = 11 # compile error: assignment to an immutable bindingValue semantics
Section titled “Value semantics”Structs and collections are value-semantic: assignment and argument passing
denote the value, not a shared alias. The compiler is free to copy-on-write.
There is no observable aliasing of mutable data outside the explicit shared-state
types Atomic[T] and Mutex[T]. This is what makes Sema’s parallelism sound
without a GIL.
mut a = [1, 2, 3]b = a # b is the value, not an aliasa.append(4) # mutating a does NOT change b# a == [1,2,3,4], b == [1,2,3]Mutation interacts with the rest of the language in three fixed ways:
- Contracts. A struct
invariantis re-checked at every mutation of a guarded field through amutbinding, and at every boundary crossing — an aggregate can never be observed with a violated invariant. - Trust. Writing a field re-labels the aggregate with the meet of its old trust label and the written value’s label — mutation can only lower trust, never launder it. See /governance/provenance/.
- Constants. Module-level plain bindings of literal or pure-
!{}initializers are compile-time constants.
Conditional expression
Section titled “Conditional expression”then if cond else else_ is an expression — only the taken branch is evaluated.
It sits below every binary operator and above lambda/=> in precedence, and
its else branch chains right-associatively:
label = "hot" if score > 0.9 else ("warm" if score > 0.5 else "cold")This is the concise form default trait methods lean on; the statement if is on
the Control Flow page.
Function types
Section titled “Function types”Functions and lambdas are first-class values. The function type is written
(T, U) -> R !{row} — the effect row is part of the type, so a parameter of
function type declares the effects its callee may perform. A higher-order function
cannot smuggle effects its own row does not admit. See
Functions & Effects.
Prelude type commitments
Section titled “Prelude type commitments”These types are language-adjacent and committed in the prelude, not user code:
Path, Duration (written as quoted duration literals — "2s", "50ms"),
Instant, JsonValue, Tensor[T], Atomic[T], Mutex[T], Task[T],
Stream[T]/Window[T], DebugSnapshot, and the structured logging surface
(log.*, print, alert). Their full APIs live in the
stdlib reference.
What’s next
Section titled “What’s next”- Sum types with behavior → Traits, Enums & Generics
- Absence and failure in depth → Error Handling
- The
~=graded operator → Operators and Similarity