soma.cell

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.

TypeExampleNotes
Int42, -1, 0arbitrary precision (i64 fast path, BigInt beyond — a slot gives it back as an Int)
Float3.14, 1.5e364-bit, scientific notation
String"hello {name}"interpolation with {}
Booltrue, false
Listlist(1, 2, 3) or [1, 2, 3]ordered
Mapmap("key", val)key-value pairs, MUST have even args
Unit()null equivalent
Duration5s, 1min, 500ms, 1hconverts to milliseconds
RecordUser { name: "Alice", age: 30 }typed map with _type field
Anyon 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 <= 50

It 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.