Quick Start Examples
The fastest way to see AILANG in action: ask an AI agent to build something.
Prerequisites
Make sure you have AILANG installed via your AI agent. See Getting Started for installation options.
Coding Challenges: Watch AI Build Applications
The AILANG plugin includes coding challenges - guided tasks where you can watch an AI agent build real applications in AILANG. This is the best way to verify AILANG works and see it in action.
Available Challenges
| Challenge | Difficulty | What It Builds |
|---|---|---|
ask-ai | Beginner | Simple AI Q&A tool |
summarize-file | Intermediate | AI-powered file summarizer |
ai-debate | Intermediate | Multi-model AI debate orchestrator |
game-of-life | Intermediate | Conway's Game of Life with HTML visualization |
How to Use Challenges
With Claude Code or Gemini CLI:
/ailang-challenge game-of-life
Or ask your AI agent directly:
Complete the AILANG Game of Life challenge. Show me each step.
What Happens
- The AI loads the challenge description
- It explores AILANG's stdlib with
ailang builtins list --verbose - It writes AILANG code step-by-step
- It type-checks with
ailang check - It runs with
ailang run --caps ... - You watch it work and see the output!
Example: Watch AI Build Game of Life
Ask your agent:
Build Conway's Game of Life in AILANG. Use std/array for the grid,
create Cell ADT with Alive/Dead, implement the rules as pure functions,
and output an HTML file that animates the evolution.
The AI will:
- Design data structures using AILANG's type system
- Implement Conway's rules with pattern matching
- Compute 60 generations using pure functions
- Generate HTML output via FS effect
- Produce a working animation you can open in a browser
This proves:
- AILANG is a real programming language
- AI agents can learn and use it correctly
- The effect system tracks capabilities (IO, FS)
- Pure functions enable deterministic computation
Suggested Learning Path
- ask-ai - Start here. Simplest AI integration (5 min)
- summarize-file - Add file I/O to AI calls (10 min)
- ai-debate - Orchestrate multiple AI calls (15 min)
- game-of-life - Pure computation showcase (20 min)
Challenge Structure
Each challenge folder contains:
challenges/<name>/
├── challenge.md # Guided prompt (what the AI sees)
├── solution.ail # Working reference
└── hints.md # Progressive hints (optional)
Challenges live in the ailang_bootstrap repo.
Language Reference Examples
The following examples show AILANG syntax for reference. You don't need to write this yourself - just ask your AI agent to build what you need!
Example 1: Hello World
The simplest AILANG program:
-- hello.ail - Simple hello world program
module examples/runnable/hello
-- Entry module with exported main function
-- Gets print prelude automatically (no import needed)
export func main() -> () ! {IO} =
print("Hello, AILANG!")
Run it:
ailang run --caps IO --entry main examples/runnable/hello.ail
Key concepts:
moduledeclaration names your fileexport func main()is the entry point-> () ! {IO}means returns unit()with IO effectprintis auto-imported from prelude (no import needed)
Example 2: Pure Functions, Recursion, and Inline Tests
AILANG is a pure functional language with built-in testing. This example shows both:
-- 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 --entry main examples/runnable/recursion_factorial.ail
Output: factorial(5) = 120
Run the inline tests:
ailang test examples/runnable/recursion_factorial.ail
Key concepts:
pure funcdeclares a function with no side effectstests [(input, expected), ...]defines inline testsshow()converts values to strings (auto-imported)++concatenates strings- Recursion is the primary iteration mechanism
Example 4: Reading Files
Use the FS (file system) effect to read files:
-- read_file.ail - Reading files with FS effect
-- Used in: docs/docs/guides/quick-start-examples.mdx
module examples/docs/read_file
import std/fs (readFile)
import std/io (println)
export func main(path: string) -> () ! {IO, FS} = {
let content = readFile(path);
println("=== File Contents ===");
println(content)
}
Run it:
ailang run --caps IO,FS --args-json '"README.md"' --entry main examples/docs/read_file.ail
Key concepts:
import std/fs (readFile)imports specific functions--caps IO,FSgrants required capabilities--args-jsonpasses arguments to main- Effects are declared in the function signature:
! {IO, FS}
Example 5: AI Integration
Call AI models directly from AILANG:
-- AI Effect Example
-- Demonstrates calling AI from AILANG
--
-- Run with stub handler (testing):
-- ailang run --caps IO,AI --ai-stub --entry main examples/runnable/ai_effect.ail
--
-- Run with Claude Haiku (requires ANTHROPIC_API_KEY):
-- ailang run --caps IO,AI --ai claude-haiku-4-5 --entry main examples/runnable/ai_effect.ail
--
-- Run with GPT-5 Mini (requires OPENAI_API_KEY):
-- ailang run --caps IO,AI --ai gpt5-mini --entry main examples/runnable/ai_effect.ail
--
-- Run with Gemini Flash (requires GOOGLE_API_KEY):
-- ailang run --caps IO,AI --ai gemini-2-5-flash --entry main examples/runnable/ai_effect.ail
--
-- The AI effect is a general-purpose AI oracle:
-- - String→string interface (JSON by convention)
-- - Pluggable handlers (stub, Anthropic, OpenAI, Google)
-- - Model lookup from models.yml (or guessed from prefix)
-- - No silent fallbacks (nil handler = error)
module examples/runnable/ai_effect
import std/ai (call)
import std/io (println)
-- Ask the AI a question and print the response
export func main() -> () ! {IO, AI} {
println("Asking AI: What is the capital of France?");
let response = call("What is the capital of France? Reply in one word.");
println("AI says: " ++ response)
}
Run it:
# Set your API key first
export ANTHROPIC_API_KEY="sk-ant-..."
# Run with AI capability
ailang run --caps IO,AI --ai claude-haiku-4-5 --entry main examples/runnable/ai_effect.ail
Supported AI models:
claude-haiku-4-5- Fast, affordable (Anthropic)claude-sonnet-4- Balanced (Anthropic)gpt-4o-mini- Fast (OpenAI)gemini-2.0-flash- Fast (Google)
Example 6: File Summarizer (Practical Tool)
Combine FS and AI effects for a useful tool:
-- summarize_file.ail - Combine FS and AI effects
-- Used in: docs/docs/guides/quick-start-examples.mdx
module examples/docs/summarize_file
import std/ai (call)
import std/fs (readFile)
import std/io (println)
func summarize(content: string) -> string ! {AI} = {
let prompt = "Summarize in 3-5 bullet points:\n\n" ++ content;
call(prompt)
}
export func main(path: string) -> () ! {IO, FS, AI} = {
println("Reading: " ++ path);
let content = readFile(path);
println("Generating summary...");
let summary = summarize(content);
println("");
println("=== Summary ===");
println(summary)
}
Run it:
ailang run --caps IO,FS,AI --ai claude-haiku-4-5 \
--args-json '"README.md"' --entry main examples/docs/summarize_file.ail
Example 7: Records and Data Types
Work with structured data:
-- records_person.ail - Records and data types
-- Used in: docs/docs/guides/quick-start-examples.mdx
module examples/docs/records_person
import std/io (println)
-- Define a record type (optional, AILANG infers types)
type Person = { name: string, age: int, city: string }
pure func greet(p: Person) -> string =
"Hello, " ++ p.name ++ " from " ++ p.city ++ "!"
pure func birthday(p: Person) -> Person =
-- Record update syntax: creates new record with updated field
{ p | age: p.age + 1 }
export func main() -> () ! {IO} = {
let alice = { name: "Alice", age: 30, city: "Copenhagen" };
println(greet(alice));
let olderAlice = birthday(alice);
println("After birthday: " ++ show(olderAlice.age))
}
Run it:
ailang run --caps IO --entry main examples/docs/records_person.ail
Example 8: Algebraic Data Types (ADTs)
Define custom types with variants and pattern matching:
-- adt_option_pattern.ail - ADT Option type with pattern matching
-- Used in: docs/docs/guides/quick-start-examples.mdx
module examples/docs/adt_option_pattern
import std/io (println)
-- Define a monomorphic Option type for integers
type IntOption = SomeInt(int) | NoneInt
-- Pattern matching on ADTs
pure func unwrapOrInt(opt: IntOption, default: int) -> int =
match opt {
SomeInt(x) => x,
NoneInt => default
}
-- Map for int -> int
pure func mapInt(opt: IntOption, f: int -> int) -> IntOption =
match opt {
SomeInt(x) => SomeInt(f(x)),
NoneInt => NoneInt
}
export func main() -> () ! {IO} = {
let some = SomeInt(42);
let none = NoneInt;
println("Some value: " ++ show(unwrapOrInt(some, 0)));
println("None value: " ++ show(unwrapOrInt(none, -1)));
let doubled = mapInt(some, \x. x * 2);
println("Doubled: " ++ show(unwrapOrInt(doubled, 0)))
}
Run it:
ailang run --caps IO --entry main examples/docs/adt_option_pattern.ail
Output:
Some value: 42
None value: -1
Doubled: 84
Example 9: Lambda Expressions
Functional programming with lambdas and higher-order functions:
-- lambdas_full.ail - Lambda expressions and higher-order functions
-- Used in: docs/docs/guides/quick-start-examples.mdx
module examples/docs/lambdas_full
import std/io (println)
export func main() -> () ! {IO} = {
-- Basic lambda
let double = \x. x * 2;
println("double(5) = " ++ show(double(5)));
-- Multi-parameter lambda (curried)
let add = \x. \y. x + y;
println("add(3)(4) = " ++ show(add(3)(4)));
-- Partial application
let add10 = add(10);
println("add10(5) = " ++ show(add10(5)));
-- Function composition
let compose = \f. \g. \x. f(g(x));
let inc = \x. x + 1;
let doubleThenInc = compose(inc)(double);
println("doubleThenInc(5) = " ++ show(doubleThenInc(5)))
}
Run it:
ailang run --caps IO --entry main examples/docs/lambdas_full.ail
Output:
double(5) = 10
add(3)(4) = 7
add10(5) = 15
doubleThenInc(5) = 11
Example 10: Conway's Game of Life
A complete implementation showing arrays, ADTs, and pure functions:
module examples/runnable/conway_grid
-- Conway's Game of Life on an Array grid
-- Exercises: Arrays (spatial state), ADTs (cell state), Inline tests (invariants)
import std/array (make as arrayMake, get as arrayGet, set as arraySet)
-- Cell state as ADT
export type Cell = Alive | Dead
-- Grid dimensions (5x5 for simplicity)
pure func gridSize() -> int { 5 }
-- Convert 2D coordinates to 1D array index
pure func toIndex(x: int, y: int) -> int
tests [((0, 0), 0), ((1, 0), 1), ((0, 1), 5), ((2, 3), 17)]
{ y * gridSize() + x }
-- Check if coordinates are in bounds
pure func inBounds(x: int, y: int) -> bool
tests [((0, 0), true), ((4, 4), true), ((-1, 0), false), ((5, 0), false)]
{ x >= 0 && x < gridSize() && y >= 0 && y < gridSize() }
-- Safely get cell (returns Dead for out-of-bounds)
pure func getCell(grid: Array[Cell], x: int, y: int) -> Cell {
if inBounds(x, y) then
arrayGet(grid, toIndex(x, y))
else
Dead
}
-- Helper: check if cell is alive (returns 1 if alive, 0 if dead)
pure func cellValue(cell: Cell) -> int {
match cell { Alive => 1, Dead => 0 }
}
-- Count alive neighbors (8-directional)
pure func countNeighbors(grid: Array[Cell], x: int, y: int) -> int {
let n0 = cellValue(getCell(grid, x - 1, y - 1));
let n1 = cellValue(getCell(grid, x, y - 1));
let n2 = cellValue(getCell(grid, x + 1, y - 1));
let n3 = cellValue(getCell(grid, x - 1, y));
let n4 = cellValue(getCell(grid, x + 1, y));
let n5 = cellValue(getCell(grid, x - 1, y + 1));
let n6 = cellValue(getCell(grid, x, y + 1));
let n7 = cellValue(getCell(grid, x + 1, y + 1));
n0 + n1 + n2 + n3 + n4 + n5 + n6 + n7
}
-- Apply Conway rules: returns 1 if cell should be alive, 0 if dead
-- 1. Any live cell with 2-3 neighbors survives
-- 2. Any dead cell with exactly 3 neighbors becomes alive
-- 3. All other cells die or stay dead
pure func shouldLive(isAlive: int, neighbors: int) -> int
tests [
((1, 0), 0),
((1, 1), 0),
((1, 2), 1),
((1, 3), 1),
((1, 4), 0),
((0, 2), 0),
((0, 3), 1),
((0, 4), 0)
]
{
if isAlive == 1 then
if neighbors == 2 || neighbors == 3 then 1 else 0
else
if neighbors == 3 then 1 else 0
}
-- Create empty grid (all Dead)
pure func emptyGrid() -> Array[Cell] {
arrayMake(25, Dead)
}
-- Set a cell in the grid (returns new grid)
pure func setGridCell(grid: Array[Cell], x: int, y: int, value: Cell) -> Array[Cell] {
if inBounds(x, y) then
arraySet(grid, toIndex(x, y), value)
else
grid
}
-- Create a blinker pattern (oscillates between horizontal and vertical)
pure func makeBlinker() -> Array[Cell] {
let grid = emptyGrid();
let g1 = setGridCell(grid, 2, 1, Alive);
let g2 = setGridCell(g1, 2, 2, Alive);
setGridCell(g2, 2, 3, Alive)
}
-- Display a cell as character
pure func cellChar(cell: Cell) -> string {
match cell { Alive => "#", Dead => "." }
}
-- Display a row of the grid
pure func showRow(grid: Array[Cell], y: int, x: int, acc: string) -> string {
if x >= gridSize() then acc
else showRow(grid, y, x + 1, acc ++ cellChar(getCell(grid, x, y)))
}
-- Display entire grid as string
pure func showGrid(grid: Array[Cell], y: int, acc: string) -> string {
if y >= gridSize() then acc
else showGrid(grid, y + 1, acc ++ showRow(grid, y, 0, "") ++ "\n")
}
-- Compute next cell state
pure func nextCell(grid: Array[Cell], x: int, y: int) -> Cell {
let current = getCell(grid, x, y);
let isAlive = cellValue(current);
let neighbors = countNeighbors(grid, x, y);
if shouldLive(isAlive, neighbors) == 1 then Alive else Dead
}
-- Compute next generation (iterate through all cells)
pure func evolveRow(grid: Array[Cell], newGrid: Array[Cell], y: int, x: int) -> Array[Cell] {
if x >= gridSize() then newGrid
else evolveRow(grid, setGridCell(newGrid, x, y, nextCell(grid, x, y)), y, x + 1)
}
pure func evolveGrid(grid: Array[Cell], newGrid: Array[Cell], y: int) -> Array[Cell] {
if y >= gridSize() then newGrid
else evolveGrid(grid, evolveRow(grid, newGrid, y, 0), y + 1)
}
pure func evolve(grid: Array[Cell]) -> Array[Cell] {
evolveGrid(grid, emptyGrid(), 0)
}
-- Main function to run and display Conway's Game of Life
export func main() -> () ! {IO} = {
print("Conway's Game of Life - Blinker Pattern\n");
print("========================================\n");
let gen0 = makeBlinker();
print("\nGeneration 0:\n");
print(showGrid(gen0, 0, ""));
let gen1 = evolve(gen0);
print("Generation 1:\n");
print(showGrid(gen1, 0, ""));
let gen2 = evolve(gen1);
print("Generation 2 (back to original):\n");
print(showGrid(gen2, 0, ""))
}
Run it:
ailang run --caps IO --entry main examples/runnable/conway_grid.ail
This example demonstrates:
- ADT pattern matching (
match cell { Alive => ... }) - Inline tests for pure functions
- Array operations for grid management
- Pure computation with no side effects
REPL: Interactive Exploration
Start the REPL to experiment:
ailang repl
Try these expressions:
λ> 1 + 2
3 :: Int
λ> "Hello " ++ "World"
Hello World :: String
λ> let double = \x. x * 2 in double(21)
42 :: Int
λ> :type \x. x + x
\x. x + x :: ∀α. Num α ⇒ α → α
λ> :quit
CLI Quick Reference
| Command | Description |
|---|---|
ailang run --caps CAPS --entry main file.ail | Run a program |
ailang check file.ail | Type-check without running |
ailang test file.ail | Run inline tests |
ailang repl | Start interactive REPL |
ailang prompt | Get AI teaching prompt |
ailang builtins list --verbose --by-module | Full stdlib docs |
Available Capabilities:
IO- Console input/outputFS- File system operationsAI- AI model callsNet- Network requestsClock- Time operationsEnv- Environment variables