Types
Types, in the reference of the Soma programming language (soma 3.2.4). A block marked ✓ is a whole program that soma check accepts; the others are excerpts.
| Type | Example | Notes |
|---|---|---|
| Int | 42, -1, 0 | arbitrary precision (i64 fast path, BigInt beyond — a slot gives it back as an Int) |
| Float | 3.14, 1.5e3 | 64-bit, scientific notation |
| String | "hello {name}" | interpolation with {} |
| Bool | true, false | |
| List | list(1, 2, 3) or [1, 2, 3] | ordered |
| Map | map("key", val) | key-value pairs, MUST have even args |
| Unit | () | null equivalent |
| Duration | 5s, 1min, 500ms, 1h | converts to milliseconds |
| Record | User { name: "Alice", age: 30 } | typed map with _type field |
| Any | on log(x: Any) | a parameter or slot value of any kind (check it with type_of(x)); every parameter needs a type — x: Any when it can be anything |
Mixed Int/Float comparisons preserve the exact integer value: an Int beyond 2^53 is not rounded to Float before comparison. NaN is unordered (<, >, <=, >= and == are false; != is true). Sorting places NaN after finite numbers in ascending order. Structural equality applies inside lists/maps; distinct and distinct_by use it and retain values containing NaN.
Nominal types: what a number counts
A complete Soma program: soma check passes on it (soma 3.2.4).
cell type Lots = Int // a nominal type over Int, Float or String
cell type Cents = Int
cell Risk {
const MAX_LOTS = Lots(50)
memory {
position: Map<String, Lots> [persistent]
invariant position >= 0 && position <= MAX_LOTS
}
on order(sym: String, lots: Lots, price: Cents) {
require lots > 0 else BadQty
let held = position.get(sym) ?? 0 // Lots: a literal takes the other side's type
require held + lots <= MAX_LOTS else OverCap // Lots with Lots
position.set(sym, held + lots)
return Cents(to_int(held + lots) * to_int(price)) // a conversion, written out
}
}soma check refuses (E0122) what mixes two nominal types — lots + price, lots <= MAX_EXPOSURE with a cap in Cents, lots * price, a Cents passed where Lots is declared, a Cents written into a slot of Lots — and a plain Int variable where a Lots stands (lots + n: write Lots(n) if n counts lots, or to_int(lots) + n). Lots with Lots gives Lots (+ - %, abs, min, max, idiv(q, 2)); scaling by a literal keeps it (q * 2); / gives a plain number. Lots(x) enters the type, to_int / to_float / to_string leave it. A value read from a Map or an Any has no type to check: meeting a nominal type it is a warning (make it a record field).
The type is checked, then erased: at run time, in storage, in JSON and in [native] a Lots is an Int, and soma verify proves the same things.
A nominal type can carry a bound, written once:
cell type Lots = Int where value >= 0 && value <= 50It becomes what soma verify already proves: a requires on each face parameter of type Lots (the caller's fault, kind requires), an ensures on each -> Lots result (kind ensures), a memory invariant on each slot that holds Lots (Map<String, Lots>, List<Lots>), and a require … else OutOfRange on each Lots parameter of a handler with no face signal. A literal outside the bound (Lots(60), const MAX = Lots(60)) is refused by soma check. The bound reads only value, literals and constants. A [native] handler takes no contract.