Lattice

A crystallization-based programming language

The Phase System

Variables in Lattice exist in phases, like matter. They start as mutable flux, and crystallize into immutable fix with freeze(). Need to change them again? thaw() brings them back to flux. Forge blocks let you build complex immutable structures through controlled mutation.

flux mutable
freeze() crystallize
fix immutable
forge_example.lat
// Forge blocks: controlled mutation that crystallizes
fix config = forge {
    flux temp = Map::new()
    temp.set("host", "localhost")
    temp.set("port", "8080")
    temp.set("debug", "true")
    freeze(temp)
}

// config is now permanently immutable
print(config.get("host"))  // "localhost"
print(phase_of(config))    // "crystal"

Structs with Callable Fields

Lattice structs can hold closures as fields, giving you flexible object-like patterns with explicit data flow.

state_machine.lat
struct VendingMachine {
    balance: Int,
    inventory: Map,
    insert_coin: Fn,
    select_item: Fn,
    dispense: Fn
}

fn make_vending_machine() -> VendingMachine {
    flux inv = Map::new()
    inv.set("cola", 150)
    inv.set("chips", 100)
    inv.set("candy", 75)

    return VendingMachine {
        balance: 0,
        inventory: inv,
        insert_coin: |self, amount| {
            self.balance + amount
        },
        select_item: |self, item| {
            let price = self.inventory.get(item)
            if self.balance < price {
                "insufficient funds"
            } else {
                "ok:${item}"
            }
        },
        dispense: |self, item| {
            let price = self.inventory.get(item)
            let change = self.balance - price
            "Dispensing ${item}! Change: ${change}"
        }
    }
}

Everything is an Expression

if/else blocks return values, ranges drive for loops, and error handling is explicit with try/catch.

fizzbuzz.lat
fn fizzbuzz(n: Int) -> String {
    if n % 15 == 0 {
        "FizzBuzz"
    } else {
        if n % 3 == 0 { "Fizz" }
        else {
            if n % 5 == 0 { "Buzz" }
            else { to_string(n) }
        }
    }
}

fn main() {
    for i in 1..21 {
        print("${i}: ${fizzbuzz(i)}")
    }
}

Closures & Higher-Order Functions

First-class closures capture their environment and compose naturally. Use map, filter, reduce, and sort to transform data.

functional.lat
fn main() {
    let data = [5, 3, 8, 1, 9, 2, 7]

    // Chain operations
    let result = data
        .filter(|x| { x > 3 })
        .map(|x| { x * 2 })
        .sort()

    print(result)  // [10, 14, 16, 18]

    // Reduce to a single value
    let sum = data.reduce(|acc, x| { acc + x }, 0)
    print("sum = ${sum}")  // sum = 35
}

String Interpolation

Embed any expression directly inside a string with ${...}. Variables, arithmetic, method calls, and nested expressions all work. Use \${ for a literal dollar-brace.

interpolation.lat
fn main() {
    let name = "Lattice"
    let version = 8

    // Variables and expressions
    print("hello ${name}")              // hello Lattice
    print("2 + 2 = ${2 + 2}")            // 2 + 2 = 4
    print("v0.2.${version}")             // v0.2.8

    // Method calls
    print("upper: ${name.to_upper()}")  // upper: LATTICE
    print("len: ${[1,2,3].len()}")     // len: 3

    // Escaped: literal ${
    print("price: \${9.99}")            // price: ${9.99}
}

Structured Concurrency & Channels

Spawn tasks inside scope blocks for parallel execution with automatic join semantics. Channels provide thread-safe communication — only crystal (frozen) values can cross thread boundaries, so the phase system prevents data races at the language level. Use select to multiplex across multiple channels with optional timeouts and defaults.

concurrency.lat
fn sum_range(start: Int, end: Int) -> Int {
    flux total = 0
    for i in start..end { total = total + i }
    return total
}

fn main() {
    let ch1 = Channel::new()
    let ch2 = Channel::new()

    // Each spawn runs on its own thread
    scope {
        spawn { ch1.send(freeze(sum_range(0, 500000))) }
        spawn { ch2.send(freeze(sum_range(500000, 1000000))) }
    }
    // Both tasks are done here

    let total = ch1.recv() + ch2.recv()
    print("total: ${total}")  // total: 499999500000
}

Built-In Robustness

Lattice catches mistakes early with runtime type checking, function contracts, and safe navigation — without sacrificing simplicity.

robustness.lat
// Runtime type checking
fn withdraw(account: Map, amount: Int) -> Int
    require amount > 0, "amount must be positive"
    ensure |result| { result >= 0 }, "balance can't go negative"
{
    return account.get("balance") - amount
}

// Optional chaining & nil coalescing
let city = user?.address?.city ?? "Unknown"

// Defer for guaranteed cleanup
fn process(path: String) {
    let fd = open(path)
    defer { close(fd) }  // runs on any exit
    // ...
}

// Result ? operator for error propagation
fn load() -> Map {
    let data = read_config()?  // unwrap ok, or return err
    let parsed = validate(data)?
    return ok(parsed)
}

Standard Library

JSON, math, regex, path utilities, string formatting, crypto, date/time, and more — over 120 builtin functions with no external dependencies.

stdlib_demo.lat
fn main() {
    // JSON
    let data = json_parse('{"name": "Lattice", "version": 0.1}')
    print(data.get("name"))  // Lattice

    // Regex
    let emails = regex_find_all(
        "[a-z]+@[a-z]+\\.[a-z]+",
        "contact alice@example.com or bob@test.org"
    )
    print(emails)  // ["alice@example.com", "bob@test.org"]

    // String interpolation
    let pi = 3.14159
    print("pi ≈ ${pi}")

    // Path utilities
    let p = path_join("src", "lang", "parser.lat")
    print("path=${p}, dir=${path_dir(p)}, ext=${path_ext(p)}")
}

TCP & TLS Built In

Raw TCP sockets and TLS connections are first-class builtins. Build HTTP clients, servers, and encrypted communication — no external libraries required.

networking.lat
fn http_get(host: String, path: String) -> String {
    let fd = tcp_connect(host, 80)
    let req = "GET ${path} HTTP/1.0\r\nHost: ${host}\r\n\r\n"
    tcp_write(fd, req)
    let response = tcp_read(fd)
    tcp_close(fd)
    return response
}

fn main() {
    // Plain HTTP
    let html = http_get("example.com", "/")
    print(html)

    // TLS for encrypted connections
    if tls_available() {
        let fd = tls_connect("api.github.com", 443)
        tls_write(fd, "GET / HTTP/1.1\r\nHost: api.github.com\r\nConnection: close\r\n\r\n")
        print(tls_read(fd))
        tls_close(fd)
    }
}

What Makes Lattice Different

A small, focused language that gets the fundamentals right.

Phase System

Variables exist as mutable flux or immutable fix. Transition with freeze(), thaw(), and forge blocks.

First-Class Closures

Closures capture their environment and work as values. Pass them to functions, store them in structs, return them from anywhere.

Structs with Methods

Struct fields can hold closures with self access, giving you object-like patterns with explicit data flow.

Expression-Based

if/else, match, and blocks are all expressions that return values. Less boilerplate, more clarity.

Structured Concurrency

scope/spawn with channels and select multiplexing. Each task gets its own thread and GC. The phase system prevents data races by design.

TCP & TLS Networking

Raw sockets and encrypted connections as builtins. Build HTTP clients and servers without external dependencies.

Error Handling

try/catch for exceptions, defer for guaranteed cleanup, and the ? operator for ergonomic Result propagation.

Runtime Type Checking

Type annotations are enforced at runtime. Function contracts with require/ensure add preconditions and postconditions.

Optional Chaining

x?.field, x?.method(), x?[index] — safely navigate nullable values. Compose with ?? for defaults.

120+ Builtins

JSON, regex, math, crypto, file I/O, path utilities, date/time formatting, and type conversion — all built in.

Self-Hosted REPL

The interactive REPL is written in Lattice itself. Multi-line input, history, and expression evaluation built in.

Bytecode VM

Source compiles to bytecode and runs on a stack-based VM by default — both files and the REPL. Full phase system support including react, bond, seed, pressure, and alloy types.

Quick Start

Lattice is written in C with minimal dependencies. Build from source in seconds.

01

Clone the repository

git clone https://github.com/ajokela/lattice.git
02

Build with make

cd lattice && make
03

Run a program or launch the REPL

./clat examples/phase_demo.lat
04

Or start the interactive REPL

./clat