Syntax & Types
Complete syntax and type system reference for Qosm.
Qosm is a statically-typed, ML-family language. Programs are sequences of top-level items separated by semicolons.
Program structure
type Color = | Red | Green | Blue; // type declaration
effect File; // effect declaration
extern read_file : String -> String ! {File}; // extern capability
let greet name = "Hello " ++ name; // let binding
Every top-level binding must end with ; — including the last one. Omitting it causes confusing parse errors.
Literals
42 // Int
3.14 // Float
"hello" // String
"""has "quotes" inside""" // Triple-quoted string
true false // Bool
∅ // Unit (empty value)
Let bindings
There are two forms of let:
// Top-level binding (semicolon, no 'in') — ONLY at file top level
let x = 1;
let y = x + 1;
// Local binding (has 'in') — use EVERYWHERE else
let z = let a = 1 in let b = 2 in a + b;
let x = e; (with semicolon) is only valid at the top level. Inside functions, match arms, or other let bodies, always use let x = e in body.
Let with parameters
Parameters on let are syntactic sugar for nested lambdas:
let add x y = x + y;
// Same as: let add = λ x -> λ y -> x + y;
let inc x : Int = x + 1; // With type annotation on param
Lambdas
λ x -> x + 1 // Basic lambda
λ x : Int y : Bool -> x // With type annotations
λ f : (Int -> Int) -> f 5 // Higher-order
You can also write lambda instead of λ.
Function application
Application is juxtaposition (space-separated), left-associative:
f x y // Same as (f x) y
map (λ x -> x + 1) [1, 2, 3] // Parens needed around lambda
Operators
| Operator | Purpose |
|----------|---------|
| + - * / % | Arithmetic |
| == != | Equality |
| < > <= >= | Comparison |
| ++ | String concatenation (NOT +) |
| && \|\| | Logical and / or |
| \|> | Pipe: x \|> f is f x |
You can define custom operators using operator characters:
let (+.) x y = x + y; // Custom operator (. allowed except as first char)
Template strings
Template strings use backticks with {{ expr }} for interpolation:
`Hello {{ name }}` // Evaluates to String
`Count: {{ to_string n }}` // Interpolations must produce String
Records
Records are row-polymorphic (extensible):
{ name: "Alice", age: 30 } // Record literal
.name person // Field access (PREFIX notation!)
{ x: 1 | base } // Record extension
Field access is prefix: .name record, NOT record.name. This is the most common syntax surprise for newcomers.
Arrays
[1, 2, 3] // Array literal
append [0] [1, 2, 3] // Concatenation: [0, 1, 2, 3]
map (λ x -> x * 2) [1, 2, 3] // Transform: [2, 4, 6]
There's no spread syntax in array literals. Use append to build arrays.
Variants
Variants are tagged unions:
None // Nullary variant
Some{value: 42} // Named field
Some 42 // Positional (single-field variants only)
Declare custom variant types with type:
type Color = | Red | Green | Blue;
type Tree<'a> = | Leaf { value: 'a } | Branch { left: Tree<'a>, right: Tree<'a> };
Pattern matching
Qosm has no if/else — use match:
match expr with (
| pattern1 -> result1
| pattern2 -> result2)
Pattern types
x // Variable (binds the value)
_ // Wildcard (ignores)
42 "hello" true // Scalar (exact match)
None // Nullary variant
Some{value: n} // Variant with named field
Some n // Variant, positional
{name: n, age: a} // Record destructuring
[] // Empty array
[x, y] // Exact-length array
[x, ...rest] // Head + tail
Booleans as patterns
Since there's no if/else, use match on booleans:
let abs x =
match x >= 0 with (
| true -> x
| false -> 0 - x);
Comments
Only line comments are supported:
// This is a comment
let x = 42; // Inline comment
Type annotations
Types appear in extern declarations and optionally on lambda parameters:
Int Float String Bool Unit // Primitives
Int -> String // Pure function
Int -> String ! {File} // Function with effect
{name: String, age: Int} // Record type
Array<Int> // Array type
Option<Int> // Type constructor
'a // Type variable (polymorphic)
No loops, no mutation
Qosm has no loops and no mutable state. Use map, filter, foldl, or recursion:
// Sum using foldl
let sum arr = foldl (λ acc x -> acc + x) 0 arr;
// Recursive factorial
let rec factorial n =
match n <= 1 with (
| true -> 1
| false -> n * factorial (n - 1));
Built-in types
Option
type Option<'a> = | None | Some { value: 'a };
match head [1, 2, 3] with (
| Some{value: x} -> x
| None -> 0)
Result
All capability calls return Result:
type Result<'ok, 'err> = | Ok { value: 'ok } | Err { value: 'err };
match http_get {url: "..."} with (
| Ok{value: body} -> body
| Err{value: e} -> .message e)
QosmError
The standard error type for capabilities:
type QosmError =
| ModelError { message: String }
| ParseError { message: String }
| PermissionError { message: String }
| FunctionError { message: String };
Custom types
type Color = | Red | Green | Blue;
type Tree<'a> = | Leaf { value: 'a } | Branch { left: Tree<'a>, right: Tree<'a> };
Type application (@Type)
Use @Type to constrain a polymorphic function's return type:
parse_json @{name: String, age: Int} """{"name": "Alice", "age": 30}"""
my_model @{category: String} `Classify: {{ text }}`
@Type must go directly on the polymorphic function — not on a wrapper.