Core
Variables, functions, closures, control flow, and pattern matching — the building blocks of every Lattice program.
// Variables — flux is mutable, fix is immutable
flux x = 10
x = x + 5
print(x) // 15
fix name = "Lattice"
print(name) // Lattice// Functions
fn greet(name: String) -> String {
return "Hello, ${name}!"
}
print(greet("world")) // Hello, world!
fn add(a: Int, b: Int) -> Int {
return a + b
}
print(add(3, 4)) // 7// Closures
fix double = |x: Int| { x * 2 }
print(double(21)) // 42
fn make_adder(n: Int) -> Fn {
return |x: Int| { x + n }
}
fix add5 = make_adder(5)
print(add5(10)) // 15// Control flow
flux x = 42
if x > 10 {
print("big")
} else {
print("small")
}
// Ternary-style
fix label = if x > 100 { "huge" } else { "normal" }
print(label) // normal// Pattern matching
fn describe(val: any) -> String {
match val {
0 => "zero",
n if n > 0 => "positive",
_ => "negative"
}
}
print(describe(5)) // positive
print(describe(0)) // zero
print(describe(-3)) // negative// While loop
flux count = 0
while count < 5 {
count = count + 1
}
print(count) // 5// For loop
for i in 0..5 {
print(i)
}
// For-each over a collection
fix names = ["Alice", "Bob", "Carol"]
for name in names {
print("Hello, ${name}!")
}// Try / catch
try {
fix result = 10 / 0
} catch e {
print("Error: ${e}")
}Phase System
Lattice's phase system gives you fine-grained control over mutability. flux declares mutable values, fix declares immutable values, and let infers the phase. Use freeze(), thaw(), and clone() for phase transitions.
// Mutable (flux) variables can be reassigned
flux data = [1, 2, 3]
data.push(4)
print(data) // [1, 2, 3, 4]
// Freeze to make immutable
fix frozen = freeze(data)
// frozen.push(5) // ERROR: cannot mutate crystal value
print(frozen) // [1, 2, 3, 4]// Thaw to make mutable again
flux thawed = thaw(frozen)
thawed.push(5)
print(thawed) // [1, 2, 3, 4, 5]// Clone preserves phase
fix original = freeze([1, 2, 3])
fix copy = clone(original)
print(copy) // [1, 2, 3]// let infers the phase from usage
let x = 42
print(x) // 42
// Phase-aware function parameters
fn process(items: Array) -> Array {
flux result = clone(items)
result.push(99)
return freeze(result)
}
fix out = process([1, 2, 3])
print(out) // [1, 2, 3, 99]// Freeze maps too
flux config = Map::new()
config["host"] = "localhost"
config["port"] = 8080
fix locked = freeze(config)
print(locked["host"]) // localhost
// locked["port"] = 9090 // ERROR: cannot mutate crystal valueStructs & Enums
Define custom data types with struct and algebraic data types with enum. Methods are attached using standalone functions with typed self parameters.
// Struct definition and instantiation
struct Point { x, y }
flux p = Point { x: 3, y: 4 }
print("${p.x}, ${p.y}") // 3, 4// Methods via impl-style functions
fn Point.distance(self: Point) -> Float {
Math.sqrt(self.x * self.x + self.y * self.y)
}
fix p = Point { x: 3, y: 4 }
print(p.distance()) // 5.0// Struct with more fields
struct Person { name, age, email }
fix alice = Person {
name: "Alice",
age: 30,
email: "alice@example.com"
}
print("${alice.name} is ${alice.age}") // Alice is 30// Enum with variants
enum Shape {
Circle(Float),
Rectangle(Float, Float)
}
flux s = Shape::Circle(5.0)
match s {
Shape::Circle(r) => print("circle r=${r}"),
Shape::Rectangle(w, h) => print("rect ${w}x${h}")
}// Enum methods
fn Shape.area(self: Shape) -> Float {
match self {
Shape::Circle(r) => Math.pi() * r * r,
Shape::Rectangle(w, h) => w * h
}
}
fix circle = Shape::Circle(5.0)
print(circle.area()) // 78.5398...
fix rect = Shape::Rectangle(4.0, 6.0)
print(rect.area()) // 24.0// Simple enums (no payloads)
enum Color {
Red,
Green,
Blue
}
fix c = Color::Green
match c {
Color::Red => print("red"),
Color::Green => print("green"),
Color::Blue => print("blue")
}// Enum variant_name / .tag()
fix s = Shape::Circle(3.0)
print(s.tag()) // CircleMath
The Math module provides common mathematical functions, constants, and random number generation.
// Basic math functions
print(Math.sqrt(144)) // 12.0
print(Math.pow(2, 10)) // 1024.0
print(Math.abs(-42)) // 42
print(Math.floor(3.7)) // 3
print(Math.ceil(3.2)) // 4
print(Math.round(3.5)) // 4// Min, max, clamp
print(Math.min(5, 3)) // 3
print(Math.max(5, 3)) // 5// Constants
print(Math.pi()) // 3.14159...
print(Math.e()) // 2.71828...
print(Math.random()) // 0.0..1.0// Trigonometry
print(Math.sin(Math.pi() / 2)) // 1.0
print(Math.cos(0)) // 1.0
print(Math.tan(0)) // 0.0
print(Math.atan2(1, 1)) // 0.7853...// Logarithms
print(Math.log(Math.e())) // 1.0
print(Math.log2(8)) // 3.0
print(Math.log10(100)) // 2.0Strings
String methods for manipulation, searching, splitting, and formatting. Lattice strings support ${expr} interpolation.
fix s = "Hello, World!"
print(s.len()) // 13
print(s.to_upper()) // HELLO, WORLD!
print(s.to_lower()) // hello, world!// Searching
fix s = "Hello, World!"
print(s.contains("World")) // true
print(s.starts_with("Hello")) // true
print(s.ends_with("!")) // true// Replace, split, trim
fix s = "Hello, World!"
print(s.replace("World", "Lattice")) // Hello, Lattice!
print(s.split(", ")) // ["Hello", "World!"]
print(" hello ".trim()) // hello// Characters and reversing
fix s = "Hello"
print(s.chars()) // ["H", "e", "l", "l", "o"]
print(s.reverse()) // olleH// String interpolation
flux name = "Lattice"
fix version = 3
print("Welcome to ${name} v${version}!")
// Welcome to Lattice v3!// Substring and repeat
fix s = "Hello, World!"
print(s.substring(0, 5)) // Hello
print(s.repeat(2)) // Hello, World!Hello, World!// Padding
print("42".pad_left(6, "0")) // 000042
print("hi".pad_right(6, ".")) // hi....Arrays
Dynamic arrays with push, pop, functional transformations, sorting, and slicing.
// Creating and modifying arrays
flux arr = [3, 1, 4, 1, 5, 9]
arr.push(2)
print(arr.len()) // 7
print(arr.contains(4)) // true// Functional operations
fix arr = [3, 1, 4, 1, 5, 9]
fix doubled = arr.map(|x: Int| { x * 2 })
print(doubled) // [6, 2, 8, 2, 10, 18]
fix evens = arr.filter(|x: Int| { x % 2 == 0 })
print(evens) // [4]
fix sum = arr.reduce(0, |acc: Int, x: Int| { acc + x })
print(sum) // 23// Sorting
fix arr = [3, 1, 4, 1, 5, 9]
fix sorted = arr.sort()
print(sorted) // [1, 1, 3, 4, 5, 9]// Slicing and indexing
fix arr = [10, 20, 30, 40, 50]
print(arr.slice(1, 4)) // [20, 30, 40]
print(arr.first()) // 10
print(arr.last()) // 50// Reverse, unique, flatten
fix arr = [1, 2, 2, 3, 3, 3]
print(arr.reverse()) // [3, 3, 3, 2, 2, 1]
print(arr.unique()) // [1, 2, 3]
fix nested = [[1, 2], [3, 4], [5]]
print(nested.flatten()) // [1, 2, 3, 4, 5]// Zip two arrays
fix names = ["Alice", "Bob", "Carol"]
fix ages = [30, 25, 28]
fix pairs = names.zip(ages)
print(pairs) // [["Alice", 30], ["Bob", 25], ["Carol", 28]]// Chaining functional operations
fix result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(|x: Int| { x % 2 == 0 })
.map(|x: Int| { x * x })
.reduce(0, |a: Int, b: Int| { a + b })
print(result) // 220Maps
Key-value maps created with Map::new() and mutated via index assignment. Maps are pass-by-value in Lattice.
// Creating and populating a map
flux m = Map::new()
m["name"] = "Alice"
m["age"] = 30
print(m["name"]) // Alice
print(m.len()) // 2// Keys, values, entries
flux m = Map::new()
m["name"] = "Alice"
m["age"] = 30
print(m.keys()) // ["name", "age"]
print(m.values()) // ["Alice", 30]
print(m.has("name")) // true
print(m.entries()) // [["name","Alice"],["age",30]]// Iteration over entries
flux m = Map::new()
m["x"] = 10
m["y"] = 20
for entry in m.entries() {
print("${entry[0]}: ${entry[1]}")
}
// x: 10
// y: 20// Merge two maps
flux a = Map::new()
a["name"] = "Alice"
a["age"] = 30
flux b = Map::new()
b["city"] = "NYC"
fix merged = a.merge(b)
print(merged.keys()) // ["name", "age", "city"]// Remove a key
flux m = Map::new()
m["a"] = 1
m["b"] = 2
m["c"] = 3
m.remove("b")
print(m.keys()) // ["a", "c"]
print(m.len()) // 2Sets
Unordered collections of unique values with union, intersection, and difference operations.
// Creating a set
flux s = Set::new()
s.add(1)
s.add(2)
s.add(3)
s.add(2) // duplicate ignored
print(s.len()) // 3
print(s.has(2)) // true
print(s.to_array()) // [1, 2, 3]// Set operations
flux a = Set::new()
a.add(1)
a.add(2)
a.add(3)
flux b = Set::new()
b.add(2)
b.add(3)
b.add(4)
print(a.union(b)) // {1, 2, 3, 4}
print(a.intersection(b)) // {2, 3}
print(a.difference(b)) // {1}// Remove from set
flux s = Set::new()
s.add("apple")
s.add("banana")
s.add("cherry")
s.remove("banana")
print(s.has("banana")) // false
print(s.len()) // 2File System
Read, write, and manage files and directories with the Fs module.
// Read and write files
Fs.write("hello.txt", "Hello, World!")
fix content = Fs.read("hello.txt")
print(content) // Hello, World!// Append to a file
Fs.append("hello.txt", "\nGoodbye!")
fix content = Fs.read("hello.txt")
print(content)
// Hello, World!
// Goodbye!// Check existence
print(Fs.exists("hello.txt")) // true
print(Fs.exists("nope.txt")) // false// Directory operations
Fs.mkdir("my_dir")
fix files = Fs.readdir(".")
for f in files {
print(f)
}// File info
fix info = Fs.stat("hello.txt")
print(info["size"])// Remove files
Fs.remove("hello.txt")
print(Fs.exists("hello.txt")) // falseJSON / TOML / YAML
Encode and decode structured data with Json, Toml, and Yaml modules.
// JSON encode and decode
flux data = Map::new()
data["name"] = "Alice"
data["scores"] = [95, 87, 92]
fix json_str = Json.encode(data)
print(json_str) // {"name":"Alice","scores":[95,87,92]}
fix parsed = Json.decode(json_str)
print(parsed["name"]) // Alice// TOML
flux config = Map::new()
config["host"] = "localhost"
config["port"] = 8080
fix toml_str = Toml.encode(config)
fix from_toml = Toml.decode(toml_str)
print(from_toml["host"]) // localhost// YAML
flux data = Map::new()
data["title"] = "My App"
data["version"] = 1
fix yaml_str = Yaml.encode(data)
fix from_yaml = Yaml.decode(yaml_str)
print(from_yaml["title"]) // My App// Read JSON from file
Fs.write("data.json", "{\"count\": 42}")
fix raw = Fs.read("data.json")
fix obj = Json.decode(raw)
print(obj["count"]) // 42HTTP
Make HTTP requests with the Http module. Supports GET, POST, and custom headers.
// GET request
fix resp = Http.get("https://api.example.com/data")
print(resp["status"]) // 200
print(resp["body"])// POST with JSON body
flux payload = Map::new()
payload["username"] = "alice"
payload["action"] = "login"
flux headers = Map::new()
headers["Content-Type"] = "application/json"
fix body = Json.encode(payload)
fix resp = Http.post("https://api.example.com/login", body, headers)
print(resp["status"])// Response handling
fix resp = Http.get("https://api.example.com/users")
if resp["status"] == 200 {
fix data = Json.decode(resp["body"])
print(data)
} else {
print("Error: ${resp[\"status\"]}")
}TCP / TLS
Low-level networking with Tcp and Tls modules for socket communication.
// TCP server
fix server = Tcp.listen("0.0.0.0", 8080)
fix conn = server.accept()
fix data = conn.read()
conn.write("Echo: ${data}")
conn.close()// TCP client
fix client = Tcp.connect("localhost", 8080)
client.write("Hello!")
fix response = client.read()
print(response) // Echo: Hello!
client.close()// TLS client
fix tls = Tls.connect("example.com", 443)
tls.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
fix resp = tls.read()
print(resp)
tls.close()Crypto
Hashing, HMAC, random bytes, and base64 encoding with the Crypto module.
// Hashing
fix hash = Crypto.sha256("hello")
print(hash) // 2cf24dba5fb0a30e...
fix md5 = Crypto.md5("hello")
print(md5) // 5d41402abc4b2a76...// HMAC
fix hmac = Crypto.hmac_sha256("key", "message")
print(hmac)// Random bytes
fix bytes = Crypto.random_bytes(32)
print(bytes.len()) // 32// Base64 encode and decode
fix encoded = Crypto.base64_encode("Hello!")
print(encoded) // SGVsbG8h
fix decoded = Crypto.base64_decode(encoded)
print(decoded) // Hello!Environment
Access environment variables, command-line arguments, and execute system processes.
// Environment variables
fix home = Env.get("HOME")
print(home)
Env.set("MY_VAR", "hello")
print(Env.get("MY_VAR")) // hello// Command line arguments
fix args = Process.args()
for arg in args {
print(arg)
}// Process execution
fix result = Process.exec("echo", ["hello", "world"])
print(result["stdout"]) // hello world
print(result["status"]) // 0// Exit the process
// Process.exit(0)
// Process.exit(1) // non-zero for errorsDateTime
Date and time operations with the DateTime module.
// Current date and time
fix now = DateTime.now()
print(now)// Unix timestamp
fix ts = DateTime.timestamp()
print(ts) // e.g., 1708876800// Formatting
fix now = DateTime.now()
fix formatted = DateTime.format(now, "%Y-%m-%d %H:%M:%S")
print(formatted) // 2024-01-15 14:30:00// Parsing a date string
fix parsed = DateTime.parse("2024-01-15", "%Y-%m-%d")
print(parsed)Regex
Regular expression matching, searching, and replacement with the Regex module.
// Match check
fix pattern = Regex.new("\\d+")
print(Regex.is_match(pattern, "abc123")) // true
print(Regex.is_match(pattern, "abcdef")) // false// Find all matches
fix pattern = Regex.new("\\d+")
fix matches = Regex.find_all(pattern, "foo42bar99baz7")
print(matches) // ["42", "99", "7"]// Replace matches
fix pattern = Regex.new("\\d+")
fix replaced = Regex.replace(pattern, "foo42bar99", "XX")
print(replaced) // fooXXbarXX// Email validation pattern
fix email_re = Regex.new("^[^@]+@[^@]+\\.[^@]+$")
print(Regex.is_match(email_re, "user@example.com")) // true
print(Regex.is_match(email_re, "not-an-email")) // falseConcurrency
Structured concurrency with scope/spawn, channels for communication, and select for multiplexing.
// Channels and scope/spawn
fix ch = Channel::new()
scope {
spawn {
for i in 1..6 {
ch.send(freeze(i))
}
ch.close()
}
spawn {
flux running = true
while running {
flux val = ch.recv()
if val == nil { running = false } else {
print("received: ${val}")
}
}
}
}// Multiple producers, single consumer
fix ch = Channel::new()
scope {
spawn { ch.send(freeze("from A")) }
spawn { ch.send(freeze("from B")) }
spawn { ch.send(freeze("from C")) }
spawn {
for i in 0..3 {
fix msg = ch.recv()
print(msg)
}
}
}// Select on multiple channels
fix ch1 = Channel::new()
fix ch2 = Channel::new()
scope {
spawn { ch1.send(freeze("hello")) }
spawn { ch2.send(freeze("world")) }
}
select {
msg from ch1 => print("ch1: ${msg}"),
msg from ch2 => print("ch2: ${msg}")
}// Parallel computation with channels
fix results = Channel::new()
scope {
spawn {
// Compute something expensive
flux sum = 0
for i in 0..1000 { sum = sum + i }
results.send(freeze(sum))
}
spawn {
// Compute something else
flux product = 1
for i in 1..11 { product = product * i }
results.send(freeze(product))
}
spawn {
fix a = results.recv()
fix b = results.recv()
print("Results: ${a}, ${b}")
}
}// Pipeline pattern
fix input = Channel::new()
fix doubled = Channel::new()
fix output = Channel::new()
scope {
// Stage 1: generate numbers
spawn {
for i in 1..6 {
input.send(freeze(i))
}
input.close()
}
// Stage 2: double each value
spawn {
flux running = true
while running {
flux val = input.recv()
if val == nil { running = false } else {
doubled.send(freeze(val * 2))
}
}
doubled.close()
}
// Stage 3: print results
spawn {
flux running = true
while running {
flux val = doubled.recv()
if val == nil { running = false } else {
print(val)
}
}
}
}
// Output: 2, 4, 6, 8, 10
Lattice