AILANG Examples
Learn AILANG through interactive examples. Each example demonstrates a key feature with real working code from the repository.
Quick Start Examples
Hello World
The simplest AILANG program with I/O effects:
-- hello.ail - Simple hello world program
module examples/runnable/hello
import std/io (print)
-- Entry module with exported main function
export func main() -> () ! {IO} = print("Hello, AILANG!")
Run it:
ailang run --caps IO examples/runnable/hello.ail
Key concepts:
-
! {IO}declares the function performs I/O -
printlnis imported fromstd/io -
--caps IOgrants I/O capability at runtime
Recursion & Pure Functions
AILANG uses recursion instead of loops for deterministic control flow:
-- recursion_factorial.ail
-- Demonstrates simple recursive function with LetRec
-- Status: ✅ Working (M-R4 recursion support)
module examples/runnable/recursion_factorial
import std/io (println)
-- Classic factorial using recursion
pure func factorial(n: int) -> int
tests [
(0, 1),
(1, 1),
(5, 120),
(10, 3628800),
(12, 479001600)
] {
if n <= 1 then 1 else n * factorial(n - 1)
}
-- Tail-recursive factorial with accumulator
pure func factorialTail(n: int, acc: int) -> int
tests [
((0, 1), 1),
((1, 1), 1),
((5, 1), 120),
((10, 1), 3628800),
((12, 1), 479001600)
] {
if n <= 1 then acc else factorialTail(n - 1, n * acc)
}
export func main() -> () ! {IO} {
let result1 = factorial(5)
let result2 = factorialTail(10, 1)
println("factorial(5) = ${show(result1)}")
println("factorialTail(10, 1) = ${show(result2)}")
}
Run it:
ailang run --caps IO examples/runnable/recursion_factorial.ail
Note: The main function is the default entrypoint. This example prints factorial results using IO.
Key concepts:
-
factorialis pure (no effects declared) - Recursion replaces loops for deterministic reasoning
-
show()converts int to string for printing
Pattern Matching
Lists & Destructuring
Pattern matching is AILANG's primary control flow mechanism:
-- list_sum.ail - List operations with pattern matching
-- Demonstrates ADT destructuring and recursion
type IntList = INil | ICons(int, IntList)
-- Sum all integers in a list using letrec for recursion
letrec sum = \list. match list {
INil => 0,
ICons(head, tail) => head + sum(tail)
} in letrec length = \list. match list {
INil => 0,
ICons(_, tail) => 1 + length(tail)
} in let numbers = ICons(1, ICons(2, ICons(3, ICons(4, ICons(5, INil))))) in (sum(numbers), length(numbers))
Run it:
ailang run examples/runnable/list_sum.ail
Output:
(15, 5)
Key concepts:
-
INilmatches empty list -
ICons(head, tail)destructures list -
letrecenables recursive function definitions - Exhaustive matching required (compiler enforces)
Algebraic Data Types (ADTs)
Define custom types with multiple constructors:
-- adt_tree.ail - Binary tree ADT with sum operation
-- Demonstrates recursive ADTs and pattern matching
module examples/runnable/adt_tree
type Tree = Leaf(int) | Node(Tree, int, Tree)
-- Sum all values in a tree (recursive pattern matching)
pure func sumTree(tree: Tree) -> int = match tree {
Leaf(n) => n,
Node(left, value, right) => sumTree(left) + value + sumTree(right)
}
-- Count nodes in tree
pure func countNodes(tree: Tree) -> int = match tree {
Leaf(_) => 1,
Node(left, _, right) => 1 + countNodes(left) + countNodes(right)
}
export func main() -> () ! {IO} =
let tree = Node(Leaf(1), 2, Node(Leaf(3), 4, Leaf(5))) in
let _ = println("Tree sum: ${show(sumTree(tree))}") in
println("Node count: ${show(countNodes(tree))}")
Run it:
ailang run --caps IO examples/runnable/adt_tree.ail
Output:
Tree sum: 15
Node count: 5
Key concepts:
-
type Tree =defines an ADT with 2 constructors -
pure funcdeclares functions without side effects - Pattern matching handles all cases
- Compiler ensures exhaustiveness
Effect System
Multiple Effects
Functions can declare multiple effects they perform:
-- effects_fs_io.ail - Demonstrates multiple effect capabilities
-- Shows combining IO and FS effects in a single function
module examples/runnable/effects_fs_io
import std/io (println)
import std/fs (fileExists, readFile, writeFile)
-- Function that combines IO and FS effects
-- Note: ! {IO, FS} declares both capabilities
export func main() -> () ! {IO, FS} {
let path = "/tmp/ailang_test.txt"
let content = "Hello from AILANG!"
-- Write a test file (FS effect)
writeFile(path, content)
-- Print confirmation (IO effect)
println("Wrote: ${content}")
-- Check if file exists (FS effect)
let fileOk = fileExists(path)
println("File exists: ${show(fileOk)}")
-- Read it back (FS effect)
let readBack = readFile(path)
println("Read back: ${readBack}")
}
Run it:
ailang run --caps IO,FS examples/runnable/effects_fs_io.ail
Key concepts:
-
! {IO, FS}declares both I/O and filesystem effects - Sequential statements use
;in block expressions - Import functions from stdlib modules
Effect Polymorphism
Functions can be generic over effects. This allows writing reusable code that works with any combination of capabilities:
-- Effect row polymorphism: {IO | e} means "IO plus any other effects"
-- This allows functions to compose while preserving effect safety
Key concepts:
- Effect rows can be extended with
{IO | e}syntax - Effect polymorphism enables reusable library functions
- Effects compose naturally when calling functions
Type System
Type Inference
AILANG infers types automatically using Hindley-Milner:
-- Showcase: Type Inference
-- The type checker automatically infers types!
module examples/runnable/type_inference
export func main() -> () ! {IO} =
let double = \x. x * 2 in
let result = double(21) in
println("Type inference: ${show(result)}")
Run it:
ailang run --caps IO examples/runnable/type_inference.ail
Key concepts:
- Type inference works across function boundaries
- Explicit annotations optional but recommended
-
:typecommand in REPL shows inferred types
Records & Subsumption
Records are structural types with width subtyping:
-- records.ail - Record examples
-- Demonstrates record literals, field access, and nested records
-- =======================
-- BASIC RECORDS
-- =======================
-- Simple record literal
{name: "Alice", age: 30}
-- Output: {name: "Alice", age: 30}
-- Record with multiple types
{id: 1, name: "Bob", active: true, score: 98.5}
-- Output: {id: 1, name: "Bob", active: true, score: 98.5}
-- =======================
-- FIELD ACCESS
-- =======================
-- Single field access
let person = {name: "Charlie", age: 25} in
person.name
-- Output: "Charlie"
-- Multiple field access
let user = {id: 123, email: "test@example.com"} in
"${show(user.id)} - ${user.email}"
-- Output: "123 - test@example.com"
-- =======================
-- NESTED RECORDS
-- =======================
-- Record with nested structure
let company = {
name: "Acme Corp",
ceo: {
name: "Jane Doe",
age: 45
}
} in
company.ceo.name
-- Output: "Jane Doe"
-- Deeper nesting
let data = {
user: {
profile: {
name: "Deep",
email: "deep@test.com"
}
}
} in
data.user.profile.email
-- Output: "deep@test.com"
-- =======================
-- RECORDS IN EXPRESSIONS
-- =======================
-- Record with computed fields
let x = 10 in
let y = 20 in
{sum: x + y, product: x * y, x: x, y: y}
-- Output: {sum: 30, product: 200, x: 10, y: 20}
-- Using record fields in calculations
let rect = {width: 5, height: 10} in
rect.width * rect.height
-- Output: 50
-- =======================
-- RECORDS WITH FUNCTIONS
-- =======================
-- Record containing lambda functions
let math = {
add: \x y. x + y,
multiply: \x y. x * y,
square: \x. x * x
} in
math.square(5)
-- Output: 25
-- Using functions from records
let ops = {
double: \x. x * 2,
triple: \x. x * 3
} in
ops.double(21)
-- Output: 42
-- =======================
-- PATTERN MATCHING
-- =======================
-- Record patterns (v0.6.2+)
-- See examples/runnable/record_patterns.ail for comprehensive examples
let person = {name: "Alice", age: 30} in
match person {
{name, age} => "${name} is ${show(age)}"
}
-- Output: "Alice is 30"
-- =======================
-- COMPLEX EXAMPLES
-- =======================
-- Record with mixed types and nesting
let config = {
app: {
name: "MyApp",
version: "1.0.0"
},
server: {
host: "localhost",
port: 8080,
ssl: false
},
features: {
debug: true,
logging: true
}
} in
"${config.app.name} v${config.app.version}"
-- Output: "MyApp v1.0.0"
-- Building a result from record fields
let point = {x: 3, y: 4} in
let distance = \p.
let dx = p.x * p.x in
let dy = p.y * p.y in
dx + dy -- Would be sqrt(dx + dy) with math functions
in
distance(point)
-- Output: 25
Run it:
ailang run examples/runnable/records.ail
Key concepts:
- Records support structural typing
- Width subtyping:
{a, b, c}⊆{a, b} - Field access:
record.field - Nested records and lambda fields supported
Int Literals at Float Type
A numeric literal such as 0 or 2 takes the type it is used at. So f >= 0, 2 * f,
a float function that returns 0, and make(3, 0) for an Array[float] all produce floats:
-- Int literals used as floats: a numeric literal like 0 or 2 takes the type it
-- is used at, so `f >= 0`, `2 * f`, `-> float { 0 }`, `[1, f, 3]` and
-- `make(3, 0)` for an Array[float] are all float values (0.0, 2.0, ...).
-- A let-bound int literal picks ONE numeric type from its uses; mixing int and
-- float for the same binding is a type error, so annotate it or use intToFloat.
module examples/runnable/float_int_literals
import std/array (make, scatterAdd)
import std/math (intToFloat)
pure func isNonNeg(f: float) -> bool = f >= 0
pure func double(f: float) -> float = 2 * f
pure func zero() -> float = 0
pure func around(f: float) -> [float] = [1, f, 3]
-- Histogram on an Array[float]: the 0 and the 1, 2, 5 literals are floats.
pure func histogram() -> Array[float] = scatterAdd(make(3, 0), [0, 0, 2], [1, 2, 5])
-- `w` is used in a float division, so the int literal 70 is the float 70.0.
pure func bmi(h: float) -> float {
let w = 70
w / (h * h)
}
-- An annotated int stays an int; convert explicitly when mixing int and float.
pure func average(total: float) -> float {
let n: int = 4
total / intToFloat(n)
}
export func main() -> () ! {IO} {
println("isNonNeg(2.5) = ${show(isNonNeg(2.5))}")
println("double(1.5) = ${show(double(1.5))}")
println("zero() = ${show(zero())}")
println("around(2.5) = ${show(around(2.5))}")
println("histogram = ${show(histogram())}")
println("bmi(2.0) = ${show(bmi(2.0))}")
println("average(10.0) = ${show(average(10.0))}")
}
Run it:
ailang run --caps IO examples/runnable/float_int_literals.ail
Key concepts:
- An int literal used at type
floatis a float value (0→0.0) - A
let-bound numeric literal picks ONE type from its uses - Mixing int and float for the same binding is a type error: annotate the binding (
let n: int = 4) and convert withintToFloat - Literal syntax: Language Syntax reference
JSON & HTTP
JSON Parsing
Parse and work with JSON data:
-- json_parsing.ail - JSON parsing example
-- Demonstrates JSON decode with convenience functions (v0.6.4+)
module examples/runnable/json_parsing
import std/io (println)
import std/json (decode, getString, getNumber)
import std/result (Result, Ok, Err)
import std/option (Option, Some, None)
-- Parse JSON and extract values using convenience functions
export func main() -> () ! {IO} {
let jsonStr = "{\"name\": \"AILANG\", \"version\": 0.6}"
match decode(jsonStr) {
Ok(obj) => {
println("Parsed JSON successfully!")
-- Extract name field using getString (combines get + asString)
match getString(obj, "name") {
Some(name) => println("Name: ${name}"),
None => println("Name not found or not a string")
}
-- Extract version using getNumber
match getNumber(obj, "version") {
Some(ver) => println("Version: ${show(ver)}"),
None => println("Version not found or not a number")
}
},
Err(e) => println("Parse error: ${e}")
}
}
Run it:
ailang run --caps IO examples/runnable/json_parsing.ail
Key concepts:
-
decodereturnsResult[Json, string] - Pattern match on JSON structure using
Ok/Err - Use
getandasStringhelpers for field access
HTTP Requests
Make HTTP requests with the Net effect:
-- http_simple.ail - Simple HTTP request example
-- Demonstrates Net effect for HTTP calls
module examples/runnable/http_simple
import std/io (println)
import std/net (httpGet)
import std/string (length)
-- Fetch a URL and print status
export func main() -> () ! {IO, Net} {
let url = "https://httpbin.org/get"
-- Simple GET request (returns body string)
println("Fetching: ${url}")
let response = httpGet(url)
println("Response length: ${show(length(response))} bytes")
}
Run it:
ailang run --caps IO,Net examples/runnable/http_simple.ail
Key concepts:
-
! {IO, Net}declares network capability -
httpGetreturns response body as string - DNS rebinding protection built-in
Data Formats
ZIP Archives
Read and inspect ZIP archives (including .docx, .xlsx, .pptx files):
-- ZIP archive reading with std/zip
-- Demonstrates listing and reading entries from ZIP archives
-- Requires FS capability and a ZIP file to operate on
--
-- Usage: ailang run --caps IO,FS --entry main zip_reader.ail
module examples/runnable/zip_reader
import std/result (Result, Ok, Err)
import std/zip (listEntries, readEntry, readEntryBytes)
-- Example: inspect a .docx file (which is a ZIP archive)
-- .docx contains: word/document.xml, [Content_Types].xml, etc.
export func main() -> int ! {IO, FS} {
let path = "test.docx"
match listEntries(path) {
Ok(entries) => {
println("Archive entries:");
-- entries is a list of strings (entry paths)
0
},
Err(msg) => {
println(msg);
1
}
}
}
Run it:
ailang run --caps IO,FS examples/runnable/zip_reader.ail
Key concepts:
-
! {FS}declares filesystem access for ZIP reading -
_zip_listEntrieslists all entries in the archive -
_zip_readEntryreads text content (XML, JSON, etc.) -
_zip_readEntryBytesreads binary content as base64
XML Parsing
Parse and query XML documents (pure functions, no effects needed):
-- XML parsing with std/xml
-- Demonstrates parsing XML and querying the tree with findAll, getText, getAttr
--
-- Usage: ailang run --entry main xml_parser.ail
module examples/runnable/xml_parser
import std/result (Result, Ok, Err)
import std/xml (parse, findAll, findFirst, getText, getTag)
-- Parse XML and extract information using tree queries
export func main() -> int ! {IO} {
let xml = "<catalog><book id=\"1\"><title>AILANG Guide</title><author>Claude</author></book><book id=\"2\"><title>Functional Design</title><author>AI</author></book></catalog>"
match parse(xml) {
Ok(root) => {
-- Find all book elements
let books = findAll(root, "book")
println("Found books:")
-- Find first title element
match findFirst(root, "title") {
Some(titleNode) => {
let titleText = getText(titleNode)
println(titleText)
},
None => println("No title found")
}
-- Get tag name of root (returns string, empty for non-Element)
let tag = getTag(root)
println(tag);
0
},
Err(msg) => {
println(msg);
1
}
}
}
Run it:
ailang run examples/runnable/xml_parser.ail
Key concepts:
-
_xml_parsereturnsResult[XmlNode, string]- pure function -
_xml_findAlland_xml_findFirstquery by tag name -
_xml_getText,_xml_getAttrextract content - XmlNode ADT:
Element | Text | CData | Comment
Regular Expressions
Match, find, capture, replace and split with std/regex, which uses the linear-time RE2 engine. See the standard library reference:
-- regex_basics.ail - std/regex regular expressions: match, find all, capture groups, replace, split.
-- Write regex escapes with a doubled backslash in AILANG strings: "\\d+" ("\d" is a parse error).
-- Import std/regex qualified (`as R`): its split clashes with std/string's split.
-- RE2 engine: linear time, no backreferences or lookaround; compile is the only fallible step.
module examples/runnable/regex_basics
import std/io (println)
import std/list (map)
import std/regex as R
export func main() -> () ! {IO} {
match R.compile("([a-z]+)=(\\d+)") {
Err(e) => println("bad pattern: ${e}"),
Ok(re) => {
-- isMatch: does the pattern occur anywhere?
println(show(R.isMatch(re, "x=1")))
println(show(R.isMatch(re, "no pairs here")))
-- findAll: every non-overlapping match, left to right
println(show(map(\m. m.text, R.findAll(re, "a=1, bb=22"))))
-- findFirst: groups holds the whole match first, then each capture group
match R.findFirst(re, "key=42") {
Some(m) => println(show(map(\g. g.text, m.groups))),
None => println("no match")
}
-- replaceAll: refer to capture groups as $1, $2
println(R.replaceAll(re, "a=1 b=2", "$2:$1"))
-- split: the text between matches
println(show(R.split(re, "x a=1 y")))
}
}
}
Run it:
ailang run --caps IO examples/runnable/regex_basics.ail
Key concepts:
-
R.compile(pattern)returns aResult. It is the only call that can fail; matching cannot - Double the backslash in AILANG strings:
"\\d+". Writing"\d"is a parse error - Import
std/regex as R, because itssplitclashes withstd/string's -
findFirstreturnsgroups: the whole match first, then each capture group.replaceAllrefers to them as$1,$2
Testing & Verification
Property-Based Differential Testing
Check a fast algorithm against a slow, obviously-correct reference on generated inputs. ailang test runs each property on 100 generated cases and prints the failing input as a counterexample:
-- Property-based differential testing: check a fast algorithm against a slow,
-- obviously-correct reference on generated inputs, before trusting it.
-- `ailang test` runs each `property` on 100 generated cases and prints the
-- failing input as a counterexample (replay with --seed). Python's stdlib has no equivalent.
module examples/runnable/property_differential_testing
import std/list (foldl, length)
import std/io (println)
-- Brute force: the best sum over every non-empty contiguous slice. O(n^2), easy to trust.
func bestFrom(xs: [int], acc: int, best: int) -> int =
match xs {
[] => best,
x :: rest =>
let s = acc + x in
bestFrom(rest, s, if s > best then s else best)
}
func bruteMaxSlice(xs: [int]) -> int =
match xs {
[] => 0,
x :: rest =>
let here = bestFrom(rest, x, x) in
let later = bruteMaxSlice(rest) in
if length(rest) == 0 then here else if here > later then here else later
}
-- Fast: Kadane's algorithm, one pass. This is the version to submit.
export func maxSlice(xs: [int]) -> int =
match xs {
[] => 0,
x :: rest =>
let step = \st y.
match st { (cur, best) =>
let c = if cur + y > y then cur + y else y in
(c, if c > best then c else best) } in
match foldl(step, (x, x), rest) { (_, best) => best }
}
test "known case" { maxSlice([-2, 1, -3, 4, -1, 2, 1, -5, 4]) == 6 }
property "fast agrees with brute force" {
forall(xs: [int]) => maxSlice(xs) == bruteMaxSlice(xs)
}
export func main() -> () ! {IO} =
println("maxSlice = ${show(maxSlice([-2, 1, -3, 4, -1, 2, 1, -5, 4]))}")
Run it:
ailang run --caps IO examples/runnable/property_differential_testing.ail
ailang test examples/runnable/property_differential_testing.ail
Key concepts:
-
forall(xs: [int]) => ...generates lists, ints and other inputs for you - A failing property prints its counterexample, replayable with
ailang test --seed N - Write the brute-force reference first, then let the property check the fast version before you trust it
Collections: Sets, Structured Keys, Batch Updates
The competitive-programming toolkit in pure AILANG: a visited set, tuple and record map keys, and a difference array. See the Stdlib Index for std/set, std/map and std/array.
Visited Set BFS
Shortest path on a grid with a visited Set[(int, int)] and a list frontier:
-- Visited set BFS: shortest path on a grid with std/set.
-- A Set[(int, int)] of visited cells plus a list frontier is the AILANG form of
-- Python's `visited = set()` + deque BFS. Name the type with `import std/set (Set)`.
module examples/runnable/visited_set_bfs
import std/io (println)
import std/set as S
import std/set (Set)
import std/array as A
import std/string (charAt, length as strLen)
import std/list (filter, flatMap, foldl, any)
func passable(g: Array[string], p: (int, int)) -> bool = match p {
(r, c) => r >= 0 && r < A.length(g) && c >= 0 && c < strLen(A.get(g, 0)) && charAt(A.get(g, r), c) == "."
}
func neighbours(g: Array[string], p: (int, int)) -> [(int, int)] = match p {
(r, c) => filter(\q. passable(g, q), [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)])
}
-- Add p to the next frontier only if it is not yet visited.
func visit(st: (Set[(int, int)], [(int, int)]), p: (int, int)) -> (Set[(int, int)], [(int, int)]) = match st {
(seen, next) => if S.member(seen, p) then st else (S.insert(seen, p), p :: next)
}
-- One BFS level per call; -1 when the frontier empties without reaching goal.
func bfs(g: Array[string], frontier: [(int, int)], seen: Set[(int, int)], goal: (int, int), dist: int) -> int =
if any(\p. p == goal, frontier) then dist else match foldl(visit, (seen, []), flatMap(\p. neighbours(g, p), frontier)) {
(_, []) => -1,
(seen2, next) => bfs(g, next, seen2, goal, dist + 1)
}
export func main() -> () ! {IO} {
let g = A.fromList(["..#....", ".##.##.", "....#..", "#.#...#", "...#..."])
println("shortest path (0,0) -> (4,6): ${show(bfs(g, [(0, 0)], S.fromList([(0, 0)]), (4, 6), 0))}")
println("goal is a wall: ${show(bfs(g, [(0, 3)], S.fromList([(0, 3)]), (0, 2), 0))}")
}
Run it:
ailang run --caps IO examples/runnable/visited_set_bfs.ail
Key concepts:
-
import std/set as Sfor the functions,import std/set (Set)to write the type (S.Set[...]does not parse) - Set elements can be tuples, records and ADT constructors
-
S.memberis O(1);S.insertcopies the set (O(n)) until persistent maps land
Cycle Detection with a Visited Set
Count moves until a (row, col, Dir) state repeats:
-- Cycle detection with a visited set of states: Set[(int, int, Dir)] from std/set.
-- A robot moves until it repeats a (row, col, direction) state, the way Python
-- does `if state in seen: break; seen.add(state)`. ADT constructors work in set keys.
module examples/runnable/cycle_detection_visited_set
import std/io (println)
import std/set as S
import std/set (Set)
type Dir = N | E | S | W
func turn(d: Dir) -> Dir = match d {
N => E,
E => S,
S => W,
W => N
}
func ahead(r: int, c: int, d: Dir) -> (int, int) = match d {
N => (r - 1, c),
E => (r, c + 1),
S => (r + 1, c),
W => (r, c - 1)
}
-- On a size x size grid with blocked cells, step forward or turn right; count
-- moves until the first repeated state.
func walk(size: int, blocked: Set[(int, int)], r: int, c: int, d: Dir, seen: Set[(int, int, Dir)], moves: int) -> int =
if S.member(seen, (r, c, d)) then moves else match ahead(r, c, d) {
(r2, c2) => if r2 < 0 || r2 >= size || c2 < 0 || c2 >= size || S.member(blocked, (r2, c2)) then walk(size, blocked, r, c, turn(d), S.insert(seen, (r, c, d)), moves + 1) else walk(size, blocked, r2, c2, d, S.insert(seen, (r, c, d)), moves + 1)
}
export func main() -> () ! {IO} {
let blocked = S.fromList([(0, 2), (2, 1)])
println("moves before a repeated state: ${show(walk(3, blocked, 0, 0, E, S.empty(), 0))}")
}
Run it:
ailang run --caps IO examples/runnable/cycle_detection_visited_set.ail
Key concepts:
-
Set[(int, int, Dir)]: an ADT constructor inside a tuple element - The AILANG form of Python's
if state in seen: break
Tuple and Record Map Keys
Tuples, records and ADT constructors as std/map keys:
-- Structured map keys: tuple keys, record keys and ADT keys in std/map (and std/set).
-- Grid coordinates (r, c) as a Map key, a record as a key (field order does not
-- matter), an ADT constructor as a key; keys iterate in a deterministic order.
module examples/runnable/map_tuple_record_keys
import std/io (println)
import std/map as M
type Color = Red | Green | Blue
export func main() -> () ! {IO} {
let dist = M.fromList([((0, 0), 0), ((0, 1), 1), ((1, 1), 2)])
println("tuple key (1, 1): ${show(M.lookup(dist, (1, 1)))}")
println("missing (5, 5): ${show(M.member(dist, (5, 5)))}")
println("keys: ${show(M.keys(dist))}")
let roles = M.insert(M.empty(), { name: "alice", team: 1 }, "lead")
println("record key: ${show(M.lookup(roles, { team: 1, name: "alice" }))}")
let counts = M.fromList([(Blue, 3), (Red, 1), (Green, 2)])
println("ADT keys: ${show(M.toList(counts))}")
}
Run it:
ailang run --caps IO examples/runnable/map_tuple_record_keys.ail
Key concepts:
- Record keys ignore field order:
{team: 1, name: "alice"}finds{name: "alice", team: 1} - Keys iterate in a deterministic canonical order
- Function or list keys are a runtime error on every operation, never a silent
false
Difference Array with accumulate
Interval coverage, a histogram and a per-bucket max, each with one array copy:
-- Difference array and histogram with std/array.accumulate (batch update, one copy).
-- accumulate(arr, [(index, value)], combine) sets out[i] = combine(out[i], value) for
-- every write in O(n + k): the pure form of Python's `diff[lo] += 1; diff[hi] -= 1`.
module examples/runnable/difference_array_accumulate
import std/io (println)
import std/array as A
import std/list (flatMap, map, mapAccumL)
import std/string (length)
-- Running sum: turns a difference array into the covered counts.
func prefix(xs: [int]) -> [int] = match mapAccumL(\s x. (s + x, s + x), 0, xs) {
(ys, _) => ys
}
-- How many intervals [lo, hi) cover each cell 0..n-1.
func coverage(n: int, intervals: [(int, int)]) -> [int] {
let corners = flatMap(\iv. match iv {
(lo, hi) => [(lo, 1), (hi, -1)]
}, intervals)
prefix(A.toList(A.accumulate(A.make(n + 1, 0), corners, \a b. a + b)))
}
export func main() -> () ! {IO} {
println("coverage: ${show(coverage(6, [(0, 3), (2, 5), (4, 6)]))}")
let words = ["b", "a", "c", "a", "b", "a"]
let idx = \w. if w == "a" then 0 else if w == "b" then 1 else 2
let hist = A.accumulate(A.make(3, 0), map(\w. (idx(w), 1), words), \a b. a + b)
println("histogram a/b/c: ${show(hist)}")
let longest = A.accumulate(A.make(2, ""), [(0, "ab"), (0, "abcd"), (1, "x")], \a b. if length(b) > length(a) then b else a)
println("max per bucket: ${show(longest)}")
}
Run it:
ailang run --caps IO examples/runnable/difference_array_accumulate.ail
Key concepts:
-
accumulate(arr, [(i, x)], combine)setsout[i] = combine(out[i], x)in list order, O(n + k) - Works for any element type; out-of-bounds indexes are errors
Building Arrays and Maps (Cost Model)
Values are immutable, so A.set, A.append, M.insert and acc ++ [x] each copy the whole structure. Doing one per loop step is quadratic. Build the structure once instead. See Arrays:
-- bulk_build_cost_model.ail - cost model: build an Array or Map ONCE instead of updating it per step.
-- A.set, A.append, M.insert and acc ++ [x] each make an O(n) copy, so one per loop step is quadratic.
-- Build once: produce the whole list (map/flatMap/range, mapAccumL for running state), then
-- A.fromList / M.fromList; apply many writes with ONE A.updateMany. Lookups stay O(1).
module examples/runnable/bulk_build_cost_model
import std/io (println)
import std/list (map, flatMap, foldl, range, mapAccumL)
import std/array as A
import std/map as M
export func main() -> () ! {IO} {
let n = 2000
-- SLOW (quadratic): one O(n) copy per step
let slowArr = foldl(\acc i. A.set(acc, i, i * i), A.make(n, 0), range(0, n))
let slowMap = foldl(\acc i. M.insert(acc, i, i * i), M.empty(), range(0, n))
-- FAST (build once): O(n) in total
let fastArr = A.fromList(map(\i. i * i, range(0, n)))
let fastMap = M.fromList(map(\i. (i, i * i), range(0, n)))
println(show(A.toList(slowArr) == A.toList(fastArr)))
println(show(M.lookup(slowMap, 1999) == M.lookup(fastMap, 1999)))
-- 2D grid: one flat Array, cell (r, c) at r * w + c
let w = 4
let grid = A.fromList(flatMap(\r. map(\c. r * 10 + c, range(0, w)), range(0, 3)))
println(show(A.get(grid, 2 * w + 1)))
-- many writes: ONE copy; a later write to the same index wins
let g2 = A.updateMany(grid, [(0, 99), (5, 7), (0, 1)])
println(show(A.get(g2, 0)))
-- running state while emitting a list: mapAccumL, never acc ++ [x]
match mapAccumL(\s x. (s + x, s + x), 0, [1, 2, 3]) {
(prefix, total) => println("${show(prefix)} ${show(total)}")
}
}
Run it:
ailang run --caps IO examples/runnable/bulk_build_cost_model.ail
Key concepts:
- Lookups cost O(1):
A.get,M.lookup,M.member, listnth - Building in one pass costs O(n):
A.fromList,M.fromList,A.updateMany,mapAccumL - A 2D grid is one flat Array, with cell
(r, c)at indexr * w + c
Advanced Examples
Higher-Order Functions
Functions are first-class values:
-- Showcase: Higher-Order Functions
-- This example demonstrates functions that take or return functions.
module examples/runnable/lambdas_higher_order
export func main() -> () ! {IO} =
let _ = println("=== Higher-Order Functions ===") in
-- Function composition
let compose = \f. \g. \x. f(g(x)) in
let double = \x. x * 2 in
let addOne = \x. x + 1 in
let doubleThenAddOne = compose(addOne)(double) in
let _ = println("Function composition:") in
let _ = println(" doubleThenAddOne(5) = ${show(doubleThenAddOne(5))}") in
let _ = println(" (first double, then add one)") in
-- Map-like function over a pair
let applyToBoth = \f. \x. \y. [f(x), f(y)] in
let square = \x. x * x in
let result = applyToBoth(square)(3)(4) in
let _ = println("") in
let _ = println("Apply function to both values:") in
let _ = println(" applyToBoth(square)(3)(4) = ${show(result)}") in
-- Function that returns a function
let makeOperation = \op. \x. \y. op(x)(y) in
let add = \x. \y. x + y in
let multiply = \x. \y. x * y in
let adder = makeOperation(add) in
let multiplier = makeOperation(multiply) in
let _ = println("") in
let _ = println("Function returning function:") in
let _ = println(" adder(5)(3) = ${show(adder(5)(3))}") in
let _ = println(" multiplier(5)(3) = ${show(multiplier(5)(3))}") in
-- Applying a function twice
let twice = \f. \x. f(f(x)) in
let increment = \x. x + 1 in
let quadruple = twice(double) in
let _ = println("") in
let _ = println("Applying function twice:") in
let _ = println(" twice(increment)(5) = ${show(twice(increment)(5))}") in
println(" quadruple(5) = ${show(quadruple(5))}")
Run it:
ailang run --caps IO examples/runnable/lambdas_higher_order.ail
Key concepts:
-
\x. x * 2is a lambda function - Functions can be passed as arguments
- Function composition:
\f. \g. \x. f(g(x)) - Closures capture environment
Mutual Recursion
Multiple functions can call each other:
-- recursion_mutual.ail
-- Demonstrates mutually recursive functions
-- Status: ✅ Working (M-R4 recursion support)
module examples/runnable/recursion_mutual
import std/io (println)
-- Mutually recursive functions to test even/odd
-- Now supported! M-TESTING-DEPS enables testing mutually recursive functions
export pure func isEven(n: int) -> bool
tests [
(0, true),
(1, false),
(2, true),
(10, true),
(17, false)
] {
if n == 0 then true else isOdd(n - 1)
}
export pure func isOdd(n: int) -> bool
tests [
(0, false),
(1, true),
(2, false),
(10, false),
(17, true)
] {
if n == 0 then false else isEven(n - 1)
}
export func main() -> () ! {IO} {
let even42 = isEven(42)
let odd42 = isOdd(42)
let even17 = isEven(17)
let odd17 = isOdd(17)
println("isEven(42) = ${show(even42)}")
println("isOdd(42) = ${show(odd42)}")
println("isEven(17) = ${show(even17)}")
println("isOdd(17) = ${show(odd17)}")
}
Run it:
ailang run --caps IO examples/runnable/recursion_mutual.ail
Key concepts:
-
pure funcdeclares functions without side effects - Functions can reference each other (mutual recursion)
- Built-in tests verify correctness:
tests [(0, true), (1, false), ...]
Loops with a Local letrec
A loop is a local recursive lambda with accumulator parameters. The recursive call is a tail call, so a million iterations run in constant stack:
-- local_letrec_loop.ail
-- A loop written as a local `letrec` with an accumulator, run over 1..1,000,000.
-- The recursive call is a tail call, so the loop runs in constant stack on
-- both engines, and it runs natively on the bytecode VM: `ailang run
-- --strict-bytecode` prints the same output as `ailang run`.
module examples/runnable/local_letrec_loop
-- Sum of the multiples of k in 1..n.
func sumMultiples(k: int, n: int) -> int {
-- The recursive call to loop is a tail call: nothing runs after it.
letrec loop = \i acc. if i > n then acc else loop(i + 1, if i % k == 0 then acc + i else acc)
loop(1, 0)
}
-- Two accumulators at once: how many numbers in 1..n are odd, and their sum.
func oddStats(n: int) -> (int, int) {
-- Both recursive calls are tail calls.
letrec loop = \i count total. if i > n then (count, total) else if i % 2 == 1 then loop(i + 1, count + 1, total + i) else loop(i + 1, count, total)
loop(1, 0, 0)
}
func describe(stats: (int, int)) -> string = match stats {
(count, total) => "${show(count)} odd numbers, sum ${show(total)}"
}
-- println is in the prelude of an entry module, so no import is needed.
export func main() -> () ! {IO} {
println("multiples of 3 up to 1000000: ${show(sumMultiples(3, 1000000))}")
println("1..1000000: ${describe(oddStats(1000000))}")
}
Run it:
ailang run --caps IO examples/runnable/local_letrec_loop.ail
ailang run --caps IO --strict-bytecode examples/runnable/local_letrec_loop.ail # same output, on the bytecode VM
Key concepts:
-
letrec loop = \i acc. ...names a lambda that can call itself - A call in tail position (the whole result of a branch) reuses the frame
- Local
letrecand preludeprintlnwithout an import both run natively under--bytecode/--strict-bytecode
Try It Yourself
In the Playground
Visit the Playground to run code interactively in your browser.
With CLI
Install AILANG and run examples locally:
# Install (macOS Apple Silicon)
curl -L https://github.com/sunholo-data/ailang/releases/latest/download/ailang-darwin-arm64.tar.gz | tar -xz
sudo mv ailang /usr/local/bin/
# Or build from source
git clone https://github.com/sunholo-data/ailang && cd ailang && make install
# Run an example
ailang run --caps IO examples/runnable/hello.ail
# Interactive REPL
ailang repl
From GitHub
Clone the repository for all 98+ examples:
git clone https://github.com/sunholo-data/ailang
cd ailang
# Run specific examples
ailang run --caps IO examples/runnable/recursion_factorial.ail
Implementation Status
This section tracks AILANG's implementation progress and is automatically updated by CI/CD.
The example verification status shows what percentage of AILANG features are fully implemented and working. Each example exercises specific language features, so the pass rate directly reflects implementation completeness.
Status
Example Verification Status
Last updated: 2026-11-04 (Auto-updated by CI)
Summary: 265 passed, 0 failed, 10 skipped (Total: 275)
Overall: 265/275 examples working (96%)
Known Limitations
Some examples may fail due to features still under development. See the maintained LIMITATIONS.md for the current list of known issues and workarounds.
Contributing Examples
Found a bug? Have a cool example to share?
Next Steps
- Try the Playground - Run code in your browser
- Read the Language Reference - Complete syntax guide
- AI Integration Guide - Use AILANG with AI agents
- Browse all examples - 98+ verified examples
See Also
- Testing Guide - Write inline tests for your examples
- Debugging Guide - Troubleshoot failing examples
- Module Execution - Run modules with full effects
- Known Limitations - Current limitations and workarounds