Skip to content

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.