Skip to main content

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

ChallengeDifficultyWhat It Builds
ask-aiBeginnerSimple AI Q&A tool
summarize-fileIntermediateAI-powered file summarizer
ai-debateIntermediateMulti-model AI debate orchestrator
game-of-lifeIntermediateConway'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

  1. The AI loads the challenge description
  2. It explores AILANG's stdlib with ailang builtins list --verbose
  3. It writes AILANG code step-by-step
  4. It type-checks with ailang check
  5. It runs with ailang run --caps ...
  6. 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

  1. ask-ai - Start here. Simplest AI integration (5 min)
  2. summarize-file - Add file I/O to AI calls (10 min)
  3. ai-debate - Orchestrate multiple AI calls (15 min)
  4. 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:

examples/runnable/hello.ail
-- 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:

  • module declaration names your file
  • export func main() is the entry point
  • -> () ! {IO} means returns unit () with IO effect
  • print is 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:

examples/runnable/recursion_factorial.ail
-- 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 func declares a function with no side effects
  • tests [(input, expected), ...] defines inline tests
  • show() 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:

examples/docs/read_file.ail
-- 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,FS grants required capabilities
  • --args-json passes arguments to main
  • Effects are declared in the function signature: ! {IO, FS}

Example 5: AI Integration

Call AI models directly from AILANG:

examples/runnable/ai_effect.ail
-- 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:

examples/docs/summarize_file.ail
-- 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:

examples/docs/records_person.ail
-- 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:

examples/docs/adt_option_pattern.ail
-- 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:

examples/docs/lambdas_full.ail
-- 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:

examples/runnable/conway_grid.ail
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

CommandDescription
ailang run --caps CAPS --entry main file.ailRun a program
ailang check file.ailType-check without running
ailang test file.ailRun inline tests
ailang replStart interactive REPL
ailang promptGet AI teaching prompt
ailang builtins list --verbose --by-moduleFull stdlib docs

Available Capabilities:

  • IO - Console input/output
  • FS - File system operations
  • AI - AI model calls
  • Net - Network requests
  • Clock - Time operations
  • Env - Environment variables

Next Steps