Contents

Language reference (mere)

The syntax and semantics of Mere as currently implemented (as of 2026-06-24 / Phase 46). &T references / region / view / effects / FFI / 4-backend codegen are all implemented. Phase 36 added 13 kinds of syntactic sugar (range / op section / :: / <| / @@ / \ lambda / string interp / ? / ?! / list comp / if let / for-in-do / while-do), substantially improving ergonomics in the ML-family tradition.


1. Lexical

Comments


// Line comment (to end of line)

Literals

KindExample
Integer0, 42, -5 (syntactically Neg (Int_lit 5))
Float1.5, 3.14, 0.0 (digits.digits; bare 1. is not a float)
Booleantrue, false
String"hello"; escapes are \n \t \\ \"
Char (length-1 str)'X'; escapes are '\n' '\t' '\\' '\'' '\"'
Unit()

A char literal 'X' is just a length-1 str (Mere has no separate char type). Convenient for dispatch like match c with | 'n' -> .... To avoid ambiguity with the type variable syntax ('a opt etc.), the lexer distinguishes 'X' (closing quote present) from 'NAME (no closing quote; alphabetic start).

Identifiers

Keywords


let rec and in if then else true false fn type signature
match with when of as _ for do while
module open import extern using region view drop

Operators and symbols


+ - * / %                arithmetic
== != < <= > >=          comparisons
&& ||                    logic (short-circuit)
++                       string concatenation
|> << >>                 pipe / function composition
<|                       reverse pipe (Phase 36): f <| x = f x
@@                       low-precedence apply (Phase 36): f @@ x = f x
::                       cons operator (Phase 36): h :: t = Cons (h, t)
..                       range literal (Phase 36): a..b = [a, ..., b-1]
?                        Option early return (Phase 36)
?!                       Result early return (Phase 36)
<-                       list comprehension generator (Phase 36)
\                        lambda shorthand (Phase 36): \x -> e
->                       function type / match-arm separator
=                        binding
: ; , .                  annotation / terminator / separator / field
( ) { } [ ]              grouping
...                      signature spread / list tail
|                        match separator / variant separator / record update / list comp

String interpolation (Phase 36)

Inside string literals, {expr} is interpolation: the lexer tokenizes recursively, and the parser expands "a {x} b" into something like "a " ++ show_or_str x ++ " b" (actually a ++ chain depending on expr's type). \{ escapes a literal brace; nested string literals inside the interpolation are forbidden (work around by binding via let first).


let n = 42 in print "answer = {show n}"        // "answer = 42"
print "escape: \{not interpolated\}"            // "escape: {not interpolated}"

2. Types

Primitives


int   float   bool   str   unit

float is IEEE 754 double. Literals with a decimal point and digits (e.g. 1.5) are float; 1 is int (bare 1. is not float but 1 + a potential .field). int and float are distinct types with no implicit coercion — use float_of_int / int_of_float explicitly; arithmetic uses f_add / f_sub / f_mul / f_div.

Composite types


t1 -> t2         function type (right-assoc: a -> b -> c == a -> (b -> c))
t1 * t2 * ...    tuple type
t list           type constructor (postfix application)
(t1, t2) result  multi type-arg
'a               type parameter (in declaration / annotation)
&R t             region-tagged reference type (Phase 1: syntax only; semantic checks come later)

3. Expressions

Literals / identifiers


42   true   "hi"   ()
x    (variable reference)

Arithmetic / comparison / logic


1 + 2 * 3                7         (* / has higher precedence)
10 / 3                   3         (integer division; 0 div is Eval_error)
10 % 3                   1         (mod; 0 div is Eval_error)
"a" ++ "b"               "ab"      (string concat)
5 <= 5                   true
1 != 2                   true
true && false            false     (short-circuit: don't eval RHS if LHS is false)
false || true            true
not true                 false     (builtin)

Phase 36 syntactic sugar at a glance

All desugar at the parser or lexer level, so the AST and beyond are unaffected. Per-form precedence is in §6.


0..5                     // range: [0, 1, 2, 3, 4] (parser directly generates this; effectively list_iota)
1 :: 2 :: []             // cons: Cons (1, Cons (2, Nil))
(+ 1)                    // op section: fn x -> x + 1
(* 2)                    // (- 1) is ambiguous with unary -, so parenthesize
(< 10)                   // comparison sections also work
\x -> x + 1              // lambda shorthand: = fn x -> x + 1
\(a, b) -> a + b         // tuple destructure OK
f <| x                   // reverse pipe: = f x
f @@ x                   // low-precedence apply: = f x; readable across line breaks
"x = {show n}"           // string interpolation (lexer level; see §1)

[expr | x <- xs, p x]                       // list comprehension (single gen + filter)
[expr | x <- xs, y <- ys, p x y]            // multi-generator (cartesian)
                                            // desugar: list_map / list_flat_map

if let pat = e then yes_branch else no_branch
  // = match e with | pat -> yes_branch | _ -> no_branch
  // (else is required; both branches share the same type)

for x in xs do body                         // = list_iter xs (\x -> body)
                                            // body must be unit-typed
while cond do body                          // = let rec __while_N = fn () ->
                                            //     if cond then (body; __while_N ()) else () in
                                            //   __while_N ()
                                            // Note: currently only runs inside an fn body (top-level is codegen-unsupported)

Option / Result early-return (? / ?!, Phase 36)

let pat = e? in body form:

Both desugar to Match in the parser:


let v = parse_int s ? in body
  ≈ match parse_int s with | Some v -> body | None -> None

let bindings


let x = 5 in x + 1                 // ident
let _ = side_effect in 1           // wildcard
let (a, b) = (3, 4) in a + b       // tuple destructure
let (a, (b, c)) = (1, (2, 3)) in a + b + c

let rec / mutual recursion


let rec fact = fn n -> if n < 1 then 1 else n * fact (n - 1) in fact 5

let rec is_even = fn n -> if n == 0 then true else is_odd (n - 1)
and is_odd     = fn n -> if n == 0 then false else is_even (n - 1)
in is_even 10

if-then-else / if-then


if cond then a else b               // standard if; a and b share the same type
if cond then print "msg"            // side-effect-only; body must be unit-typed

with (scope-bound resources with Drop, Phase 3.1)

with c = v in body is for resources with Drop (DB connections / file handles / mutexes etc.). The bound value's type must be a drop type ...-declared Drop type (use let for Trivial values). At scope end, the value's close: unit -> unit field is invoked (no-op if absent). Multiple bindings close in LIFO order.


drop type Conn = { id: int, close: unit -> unit };
let mk_conn = fn id ->
  Conn { id = id, close = fn () -> print ("close " ++ show id) };

with c = mk_conn 1 in c.id
// Result: 1. At scope end, "close 1" is printed.

with c1 = mk_conn 1, c2 = mk_conn 2 in c1.id + c2.id
// Result: 3. Prints "close 2" → "close 1" (LIFO).

with x = 5 in x + 1    // ERROR: int isn't a Drop type. Use `let`.

Design notes: implements option (i) from the internal design notes — "region is strict-Trivial; Drop is managed via with".

region (Phase 2: syntax + value expression &R v + escape check)

See memory-model.md for the memory-management concepts, comparisons, and Mere's overall strategy.


region R { body }                   // bring R into scope as a region name; evaluate body
region R { region S { ... } }       // nesting OK

fn (x: &R int) -> x                  // `&R T` reference type (R is a region name)
&R 5                                 // value expression: tag 5 as `&R int`
let x: &R int = &R 5 in ...          // combined with explicit annotation

Current semantics (Phase 2):

Escape check examples:


region R { 42 }                      // OK: int doesn't contain R
region R { let x = &R 5 in 42 }      // OK: `&R int` used inside, but result is int
region R { &R 5 }                    // ERROR: result is `&R int`; R leaks out
region R { (&R 1, 2) }               // ERROR: `&R int` inside a tuple

`R.alloc(v)` sugar (Phase 2.5): inside a region, R.alloc(expr) is syntactic sugar for &R expr. The desugaring only happens when R is a lexically enclosing region name (ordinary obj.alloc(...) field accesses keep working).


region R {
  let x = R.alloc(5) in              // == let x = &R 5 in ...
  let p = R.alloc((1, 2)) in
  42
}

`Trivial[R]` constraint (Phase 2.6): only types without Drop semantics (Trivial) can be placed in a region. Drop types are declared with drop type Name = ...; including such a type in a region (&R v / R.alloc(v) / view fields) is a type error. This is "a constraint that allows bulk region freeing"; caps that need Drop (DB connections / file handles etc.) are separately managed by a future with expression.


drop type Conn = { id: int };

let c = Conn { id = 1 } in c.id      // OK: Drop types are usable outside a region

region R {
  &R Conn { id = 1 }                  // ERROR: Trivial[R] violated
}

view Holder[R] { c: Conn };
region S { Holder { c = ... } }       // ERROR: view field has a Drop type

region R {
  &R (fn (c: Conn) -> c.id)           // OK: function types are Trivial (closure values)
}

`Trivial[R]` is implicitly the default: ordinary types (int / str / record / tuple / variant / Vec[R, T] / &R T / closure etc.) are automatically `Trivial[R]`. Users do not need to declare impl Trivial[R] for X { } (a future trait system may revisit this; see the internal design notes §3). The sole exception is types declared with drop type — they break Trivial[R] at every position they structurally appear (contains_drop_type walker in lib/typer.ml). So the judgment scheme is the simple "default-Trivial + drop-blacklist". Full trait-system rollout (DEFERRED §3.1) and explicit impl Trivial[R] syntax (§6.1) are linked in the design but don't affect the current implementation.

view (Phase 2.4: declaration + region enforcement + type-tag propagation)


view V[R] of T { f1: T1, f2: T2, ... };   // view type over region R (with explicit inner type T)
view V[R] { f1: T1, ... };                // `of T` is optional

view V[R] of T { ... } is a data declaration with a region parameter. In Phase 2.4:


view Node[R] of int { value: int, next: int };
region R { let n = Node { value = 1, next = 0 } in n.value }       // 1
region MyArena { let n = Node { value = 7, next = 0 } in n.value } // 7 (R → MyArena)
let n = Node { value = 1, next = 0 } in ...                        // ERROR: must be inside a region block

view Slot[R] { item: &R int };
region S { 
  let s = Slot { item = &S 42 } in     // s : Slot[S]
  let take_s = fn (x: &S int) -> 99 in
  take_s s.item                         // s.item : &S int → 99
}

region S { Slot { item = &T 42 } }     // ERROR (region mismatch)
region S { Cell { v = 1 } }            // ERROR: Cell[S] cannot leave region S

Planned tightening for later phases:

See memory-model.md and the internal design notes.

Functions + using [cap] syntactic sugar

using [cap1, cap2, ...] is a sugar that eases the repeated partial-application patterns of cap-passing style. Caps are expanded as the outermost curried args.


fn x using [logger] -> body
// ≡ fn logger -> fn x -> body

Callers can immediately get a T -> U with the cap embedded via f cap, ready to pass to higher-order functions like map:


let log_x = fn x using [logger] -> logger (show x);
let bound = log_x my_logger;    // bound : int -> unit
iter bound [1, 2, 3];

Functions


fn x -> x + 1                       // single arg (type-inferred)
fn (x: int) -> x + 1                // single arg (annotated)
fn (x: int, y: int) -> x + y        // multi-arg (desugared to currying)
fn (a, b, c) -> a + b * c           // multi-arg, no annotations
fn () -> 42                         // no args (internally _u : unit)

Application / partial application


inc 5
add 3 4                             // = (add 3) 4
let inc1 = (+) 1 in ...             // turning operators into functions is not yet supported (use a curried fn)

Tuples / records / lists


(1, 2, 3)                           // tuple

type Point = { x: int, y: int };
let p = Point { x = 3, y = 4 } in p.x + p.y           // record
let p2 = { p | x = 100 } in p2.x                       // record update

type 'a list = Nil | Cons of 'a * 'a list;
[1, 2, 3]                           // list literal sugar = Cons (1, Cons (2, Cons (3, Nil)))
[]                                  // = Nil

Sum types / constructors / match


type 'a opt = None | Some of 'a;

match Some 42 with
| None -> 0
| Some n when n > 10 -> 1000
| Some n -> n + 1

match xs with
| []          -> "empty"
| [h, ...t]   -> "head + rest"
| [a, b, c]   -> "exactly three"

match x with
| (a, b) as p when a < b -> p         // as-pattern: bind whole to p
| _                      -> (0, 0)

match day with
| 1 | 2 | 3 | 4 | 5 -> "weekday"     // or-pattern
| 6 | 7             -> "weekend"
| _                 -> "invalid"

Block / side-effect sequencing


{ }                                 // → unit
{ e1; e2; e3 }                      // → eN; e1..e_(N-1) are discarded (sugar for let _ = ... in chains)

Function composition / pipe


5 |> inc |> dbl                     // = dbl (inc 5); left-assoc; lowest precedence
inc << dbl                          // = fn x -> inc (dbl x); right-assoc
inc >> dbl                          // = fn x -> dbl (inc x); right-assoc

Type annotation


(42 : int)                          // expressive; must agree with the existing type
((fn x -> x + 1) : int -> int) 5    // function-typed annotation

Signature alias (function-argument bundling)


signature ctx = (db: int, log: int);

let save = fn (...ctx, order: int) -> db + log + order in
save 100 10 5                       // 115

4. Patterns

KindSyntaxExample
Wildcard__
Variablenamen, xs
IntegerN0, 42
Booleantrue / false
String"...""foo"
Unit()
Tuple(p1, p2, ...)(a, b), (a, (b, c))
ConstructorName or Name sub_patNone, Some x, Cons (h, t)
List[] / [a, b, c] / [h, ...t] / [..._]
RecordName { f1 = p1, f2 = p2 }Point { x = 0, y = py }; partial OK
aspat as nameCons (h, t) as whole
orp1p2123; both branches bind the same names + types

Guards (in match)


match x with
| n when n > 0 -> "positive"
| _            -> "non-positive"

5. Top-level declarations

let / let rec


let x = 5;                          // ident form
let (a, b) = (3, 4);                // pattern form
let _ = print "init";               // wildcard is fine

let rec fact = fn n -> ... ;
let rec is_even = ... and is_odd = ... ;

Type declarations


// 1. Sum type (variant)
type 'a opt = None | Some of 'a;
type ('a, 'b) result = Ok of 'a | Err of 'b;

// 2. Record
type Point = { x: int, y: int };
type 'a Box = { value: 'a };

// 3. Type alias
type UserId = int;
type Pair = int * int;
type 'a Stack = 'a list;

Disambiguation:

signature


signature ctx = (db: int, log: int);
// Expanded by `fn (...ctx, x: int) -> ...` (parse-time)

6. Operator precedence (low → high)

PrecedenceOperatorsAssociativity
1 (low)let, if, fn, match, with, for, while-
2@@ (low-precedence apply, Phase 36)right
3> / < (Phase 36)left / right
4<<, >>right
5left
6&&left
7==, !=, <, <=, >, >=non-associative
8:: (cons, Phase 36)right
9.. (range, Phase 36)non-associative
10+, -, ++left
11*, /, %left
12unary --
13? / ?! (postfix, Phase 36)postfix
14function applicationleft
15 (high)atom / (...) / [...] / {...} / .field / op section (+ N) / \x -> e / "...{expr}..."-

expr : type (annotation) is applied once at the outermost level.


7. Evaluation model

Copy semantics (implicitly default)

Mere has no explicit "copyable" marker like Rust's Copy trait. Instead, the following implicit rules:

So Mere's Copy/Linear distinction is realized via three layers — Drop types / OwnedVec / everything else — without explicit Copy/Linear trait annotations. Design room remains to introduce T: Copy / T: Linear type bounds later (linked to the trait system §3.1), but with no dogfood signal, it's confirmed-deferred (same §6.4).


8. Known constraints (2026-06-24)

Items previously listed as "not implemented" were implemented incrementally through Phases 14-36; the following remain:

9. Status summary


For detailed behavior, see examples/ and test/test_basic.ml.