Hello, World
Every application declares def new(args string[]) int - its return value is the process exit code.
application Hello {
def new(args string[]) int {
println("Hello, 🌍")
return 0
}
}
String Interpolation
Any expression goes inside {}. A value whose type implements str()
formats itself - no explicit conversion needed.
module greet {
interface IStringer {
def str() string
}
type Thing {
def str() string {
return "Thing"
}
}
}
application Interpolation {
use greet
def new(args string[]) int {
let n = 3
let t = greet.Thing{}
let si greet.IStringer = t
println("n={n}") // n=3
println("t={t}") // t=Thing
println("sum={n + n * 2}") // sum=9
return 0
}
}
Arrays & Maps
Arrays support set-style operators and whole-array arithmetic; slices are views into the same storage. Maps iterate as key/value pairs.
application Collections {
def new(args string[]) int {
let a = int[1, 2, 3, 4]
let b = int[3, 4, 5, 6]
println(a & b) // [3, 4] intersection
println(a | b) // [1..6] union
println(a - b) // [1, 2] difference
println(a[-2:]) // [3, 4] last two
a[:] *= 10 // scale every element in place
println(a) // [10, 20, 30, 40]
let stock = map<string, int>{
"widget": 12,
"gasket": 3,
}
stock["bolt"] = 40
for let (name, count) in stock {
println("{name}: {count}")
}
return 0
}
}
Regex
Regexes are a primitive with their own literal syntax, not a library type - flags go after the
closing slash, and captures are referenced as $1, $2 in replacements.
application Regex {
def new(args string[]) int {
let r = /[0-9]+/
r.test("a1b") // true
r.matchAll("a1b22c") // ["1", "22"]
r.split("a1b22c") // ["a", "b", "c"]
r.replace("a1b22c", "X", -1) // "aXbXc"
// the g flag replaces every match without a count
let rg = /[0-9]+/g
rg.replace("a1b22c", "X") // "aXbXc"
// capture groups, reordered in the replacement
let cap = /(a)([0-9]+)/
cap.replace("a12 b a3", "$2-$1", -1) // "12-a b 3-a"
// strings accept a regex directly
"hello123".contains(/[0-9]+/) // true
return 0
}
}
Big Numbers
bigint is arbitrary-precision and uses the same operators as any other number -
no special call syntax for arithmetic that would overflow a machine integer.
application BigNumbers {
def new(args string[]) int {
let x bigint = bigint(0x1234567890abcdef)
let y bigint = bigint(7)
// exponentiation on a value far past 64 bits
let z bigint = (x**3) - (3*x) + y
if (x < z) && (z != x) {
println(z)
}
return 0
}
}
A literal too large for int infers bigint on its own - no cast needed.
Pattern Matching
match destructures tuples and struct shapes, and each case can carry a guard after
a ;.
module shapes {
struct User {
id int
email string
}
def classify(x int) string {
match x {
case 0:
return "zero"
case (n); n > 0:
return "positive: {n}"
default:
return "negative"
}
}
def sum(t (int, int)) int {
match t {
case (a, b):
return a + b
default:
return 0
}
}
def userId(u User) int {
match u {
case {id int, email string}; id > 0:
return id
default:
return 0
}
}
}
Error Handling
A function that can fail returns !T. try propagates the error to the
caller; onerror recover handles it locally.
module inventory {
struct Item {
name string
stock int
}
def restock(item Item, qty int) !Item {
if qty <= 0 {
fail "quantity must be positive"
}
return Item{name: item.name, stock: item.stock + qty}
}
}
application Warehouse {
use inventory
def new(args string[]) int {
let item = inventory.Item{name: "widget", stock: 12}
// recover with a fallback value
let a = inventory.restock(item, -1) onerror recover item
// inspect the error before deciding
let b = inventory.restock(item, 8) onerror (err error) {
println("restock failed: {err.message}")
recover item
}
println(b.stock)
return 0
}
}
Contracts
expects runs before the body, provides runs after - on every return
path. A failed assertion becomes an error, so the function returns !T.
module accounts {
def withdraw(balance int, amount int) !int {
expects {
assert amount > 0 onerror fail "amount must be positive"
assert amount <= balance onerror fail "insufficient funds"
}
let remaining = balance - amount
return remaining
provides {
assert remaining >= 0
assert remaining < balance
}
}
}
Transactions
A function declares how to save and undo its own state with begin and
rollback. Those blocks lie dormant until the function is called from inside a
transaction - then any fail unwinds every rollback automatically.
application Ledger {
let state int = 0
def apply(x int) {
begin {
state = state + x
}
rollback {
state = state - x
}
state = state + 1
}
def boom() !int {
fail "boom"
}
def run() !none {
transaction {
apply(5)
let v = try boom() // fails - every rollback runs
println(v)
} onerror (err error) {
println("rolled back: {err.message}")
recover none
}
return none
}
def new(args string[]) int {
try run()
return 0
}
}
Generics
A where block adds members that only exist when the type argument satisfies the
constraint - User<Admin> has permissions,
User<string> doesn't.
module demo {
struct Pair<A, B> {
first A
second B
}
type Box<T> {
value T
def new(v T) { self.value = v }
def get() T { return self.value }
}
type Admin {}
type Account {}
type User<T> {
name string
where T is Admin | Account {
permissions string[]
def tag() string { return "privileged" }
}
}
def run() int {
let p Pair<int, string> = Pair<int, string>{first: 1, second: "hi"}
let b = new Box<int>(10)
let c = new Box<string>("x")
let u User<Admin> = new(name="bob")
return b.get() + c.get().len() + u.permissions.len()
}
}
Operator Overloading
An infix method binds a type to an operator symbol, so instances of it work with
ordinary operator syntax - including shifts and rotations.
module bits {
type Flags {
value int
infix def | (other Flags) Flags {
return Flags{value: value | other.value}
}
infix def & (other Flags) Flags {
return Flags{value: value & other.value}
}
infix def <<< (count int) Flags {
return Flags{value: value <<< count}
}
}
}
application Bits {
use bits
def new(args string[]) int {
let read = bits.Flags{value: 1}
let write = bits.Flags{value: 2}
let rw = read | write // calls infix def |
let hi = rw <<< 4 // calls infix def <<<
// the standard library does this too - strings repeat with *
println("ha" * 3) // hahaha
return 0
}
}
Lambdas
param => expr is shorthand for an anonymous function. Parameter types are
inferred when the callback's shape is already known.
application Lambdas {
def new(args string[]) int {
let a = int[1, 2, 3]
let doubled = a.mapElem(v => v * 2)
let labels = a.mapElem(v => sprint(v))
// an anonymous function that names itself can recurse
let fib = def fib(n int) int {
return n when n < 2
return fib(n-1) + fib(n-2)
}
println(doubled.len())
println(fib(10))
return 0
}
}
Parallel Loops
forall spreads iterations across every available CPU core. Order isn't
guaranteed; finish runs once all workers are done.
application Parallel {
def new(args string[]) int {
let values = [10, 20, 30, 40]
forall let (idx, v) in values {
println("worker got {v} at {idx}")
} finish {
println("all workers done")
}
return 0
}
}
Async & Channels
await composes with &&/|| over completion state:
|| resumes on the first to finish, && waits for both.
module work {
async def slow() int {
let acc = 0
for let i in 0..2000000 {
acc = i
}
return 7
}
async def fast() string {
return "ok"
}
}
application Concurrency {
use work
def new(args string[]) int {
let a = work.slow()
let b = work.fast()
// resume as soon as either finishes
let first = await (a || b)
println("string won") when first is string
// futures can be cancelled or given a deadline
let stuck = work.slow().timeout(50ms)
let dropped = work.slow().cancel()
// a channel between workers
let ch channel<int> = new(size=4)
ch <- 42
ch.close()
select {
case ch:
println(<-ch)
default:
println("nothing ready")
}
// or drain a channel in parallel across cores
forall let v in ch {
println(v)
} empty {
println("channel was empty")
}
return 0
}
}
Value Tags
Tags travel with a value's identity - through calls, array slots and channels - while copies start clean. A gate function refuses anything that was not marked as signed.
module demo.tags {
type Request {
id int
def new(v int) {
id = v
}
}
def deliver(message string) !none {
fail "unsigned message" unless message.tags().has("signed")
println("delivered: {message}")
return none
}
}
application TagsDemo {
use demo.tags::(*)
def new(args string[]) int {
let message = "hello"
message.tags().set("+signed")
deliver(message) onerror (err error) {
println("rejected: {err.message}")
recover none
}
let copy = message + "!" // a new value: no tags
deliver(copy) onerror (err error) {
println("rejected: {err.message}")
recover none
}
let request = new Request(7)
request.tags().add("Network/primary").add("pii")
let queue channel<Request> = new(size=1)
queue <- request
let got = <-queue
if got is Request {
for let tag in got.tags().list() {
println(tag) // Network/primary, then pii
}
}
return 0
}
}
Services
@commit() mutates service state and emits a typed event; @on subscribes
to it. @query() reads a snapshot.
module demo {
use std.services::(*)
service Storage {
opened int
@query()
def openCount() int {
return opened
}
@commit()
def open(id int) int {
opened += 1
return id
}
}
@on(Storage.open)
def logOpen(ev Event<int>) {
println("opened {ev.result}, total {Storage.openCount()}")
}
}
Custom Annotations
meta def defines a new annotation. The first parameter's context type decides
what it can decorate - and a parameter annotation can rewrite the argument before the body
ever runs:
module demo {
use std.reflection as reflection
meta def setTo(ctx reflection.ParameterMetaContext, value int) {
ctx.set(value)
}
def id(@setTo(42) x int) int {
return x
}
def run() bool {
return id(1) == 42 // the argument was rewritten on the way in
}
}
Annotations are also discoverable at runtime, so a registry can find everything tagged with a given annotation without any string lookups or manual registration:
module demo {
use std.reflection
meta def mark(target reflection.DeclarationMetaContext, name string) {}
@mark("t")
type Thing {}
@mark("f")
def handler(x int) int {
return x
}
def run() int {
let hits = reflection.attrs(mark)
return hits.len() // 2 - the type and the function
}
}
And metadata attached by an annotation stays readable at runtime, which is how the JSON encoder finds its field names:
module demo {
use std.reflection as reflection
use std.data.encoding as encoding
type T {
@encoding.json(name="a", omitempty=true)
x int
}
def run() bool {
let t = T{x: 1}
let md = reflection.fieldMetadataOf(t, "x")
return md.load("encoder.json.name") == "a"
}
}
SIMD
vec4<T> and friends map to hardware SIMD where available - arithmetic runs
across all lanes at once, and comparisons return a mask for branchless selection.
application Simd {
def new(args string[]) int {
let data = float[1.0, 2.0, 3.0, 4.0]
let v vec4<float>
let a = v.load(data, 0)
let w vec4<float>
let b = w.splat(2.0)
let scaled = a * b // all four lanes at once
let big = a.gt(b) // mask4<float>
let picked = big.blend(a, b) // branchless select
scaled.store(data, 0)
return 0
}
}
C Interop
Annotate a module and every body-less def inside binds to that library - this is
how the standard library's OpenGL bindings work.
@link("GL")
module gl {
const GL_TRIANGLES uint32 = 0x00000004
def glClear(mask uint32);
def glViewport(x, y, width, height int);
}
@c("libm.so")
module libm {
def sin(x float64) float64;
def cos(x float64) float64;
}
GPU Kernels
A cuda def is a CUDA kernel with ADM parameters; arrays are copied to the device and back around the launch, and the same program runs on the CPU when there is no GPU.
application Brighten {
use std.gpu
cuda def brighten(pixels byte[], amount int) !none {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < pixels_len) {
int v = pixels[i] + amount;
pixels[i] = v > 255 ? 255 : v;
}
}
def new(args string[]) int {
let image = byte[10, 120, 250]
brighten(image, 40) onerror recover none
println(image[0], image[1], image[2]) // 50 160 255
println("ran on {gpu.deviceName()}") when gpu.available()
return 0
}
}
Data reused across launches goes into a DeviceBuffer with gpu.upload; see GPU kernels in the reference.
Collections
std.data.collections has the usual structures as generic types; every one iterates with for.
application Words {
use std.data.collections::*
def new(args string[]) int {
let counts = new HashMap<string, int>()
for let word in "the cat and the hat".split(" ") {
counts.set(word, counts.get(word, 0) + 1)
}
let repeated = new Deque<string>()
for let entry in counts {
repeated.pushBack(entry.key) when entry.value > 1
}
println(repeated.popFront()) // the
let seen = new BloomFilter(1024)
seen.add("cat")
println(seen.mightContain("cat")) // true
return 0
}
}
Semantic Versions
std.data.semver parses versions, compares them by precedence and checks ranges in the usual ^, ~ and || notation.
application Versions {
use std.data.semver::*
def new(args string[]) int {
let v = parse("1.4.0-rc.1")
return 1 when v is error
println(v.major) // 1
println(v < "1.4.0") // true: a prerelease precedes the release
println(v.satisfies("^1.2.0")) // false: prereleases need an opt-in
println(v.satisfies(">=1.4.0-alpha")) // true
println(v.canonical()) // 1.4.0-rc.1
return 0
}
}
Plugins
A plugin exports part of what it uses and builds into a single .admplugin package; adm doc --plugin shows what is inside.
@manifest(version = "1.2.0")
plugin ImageTools {
use (
acme.imaging
acme.imaging.filters
)
export (
acme.imaging.filters
acme.imaging::(Image, decode)
)
}
adm build writes ImageTools.admplugin; the Plugins page covers the package layout and the shared runtime.
Tests
check suites live next to the code they verify. They're type-checked with the rest
of the program and dropped from release builds.
module std.os {
check "Path" {
@test()
def TestExtension() {
assert extension("/path/to/file.txt") == "txt"
assert extension("file") == ""
}
@benchmark()
def BenchBase() {
base("/path/to/file.txt")
}
}
}
Run them with adm test, or benchmarks with adm test --bench.