Skip to main content

AILANG AI Teaching Prompt (v0.3.8)

CRITICAL: You MUST write code in AILANG syntax. This is NOT Python, NOT Rust, NOT JavaScript.

You are writing code in AILANG, a pure functional programming language with Hindley-Milner type inference and algebraic effects.

⚠️ CRITICAL: What AILANG is NOT

DO NOT generate code like this - these will FAIL:

WRONG - Single-line statements without module:

PRINT 5 % 3

Every program MUST start with module benchmark/solution

WRONG - Imperative style with loops:

loop {
x = read();
if (x == "") { break; }
print(x);
}

AILANG has NO loop, break, while, for. Use recursion!

WRONG - Python/JavaScript syntax:

for i in range(10):
print(i)

This is not Python. Use functional recursion.

WRONG - Assignment statements:

x = 10;
x = x + 1;

No mutable variables. Use let bindings.

✅ CORRECT - Functional AILANG:

module benchmark/solution

import std/io (println)

export func loop(n: int) -> () ! {IO} {
if n > 10
then ()
else {
println(show(n));
loop(n + 1)
}
}

export func main() -> () ! {IO} {
loop(1)
}

MANDATORY Structure

EVERY AILANG program MUST have this structure:

  1. Module declaration: module benchmark/solution (first line)
  2. Imports: import std/io (println) (if using IO)
  3. Functions: export func main() -> () ! {IO} { ... }

IMPORTANT SYNTAX RULES:

  • Use func NOT fn, function, or def
  • Use type Name[a] = Constructor(a) | Constructor2 NOT type Name { } or enum
  • NO namespace syntax (::), just use constructor names directly
  • Semicolons REQUIRED between statements in blocks
  • Pattern matching uses => NOT : or ->
  • NO for, while, var, const, let mut, or any imperative constructs

Current Version: v0.3.8 (October 2025)

✅ WHAT WORKS:

  • Module declarations - module path/to/module
  • Function declarations - export func name(params) -> Type { body }
  • Anonymous functions - func(x: int) -> int { x * 2 } (inline lambdas)
  • Recursive lambdas - letrec fib = \n. if n < 2 then n else fib(n-1) + fib(n-2) in ...
  • Numeric conversions - intToFloat(42), floatToInt(3.14)
  • Auto-import std/prelude - NEW: Zero imports needed for comparisons (<, >, ==, !=)
  • Record updates - NEW: {base | field: value} functional updates
  • Multi-line ADTs - NEW: Optional leading pipe type Tree = | Leaf | Node
  • Import statements - import std/io (println), import std/clock (now, sleep), import std/net (httpGet)
  • Pattern matching - Constructors, tuples, lists, wildcards, guards (if conditions)
  • Effect system - ! {IO, FS, Clock, Net} for side effects with capability security
  • ADTs - Algebraic data types: type Option[a] = Some(a) | None
  • Recursion - Self-recursive and mutually-recursive functions with stack overflow protection
  • Block expressions - { stmt1; stmt2; result } for sequencing
  • Records - Record literals, field access, updates, subsumption
  • Type system fixes - Modulo operator (%) and float comparison (==) both work correctly

⚠️ LIMITATIONS:

  • ⚠️ NO for/while loops - use recursion
  • ⚠️ NO var - everything is immutable
  • ⚠️ NO error propagation operator ? (yet)
  • ⚠️ NO custom HTTP headers (OpenAI/Claude APIs blocked until v0.4.0)
  • ⚠️ NO list spread patterns [x, ...rest] (yet) - use Cons constructor
  • ⚠️ show is a builtin - do NOT import it

📋 IMPORT CHECKLIST - Read Before Writing Code!

NEW in v0.3.6: std/prelude is AUTO-IMPORTED!

You NO LONGER need to import Ord or Eq for comparison operators:

module benchmark/solution

import std/io (println)

export func main() -> () ! {IO} {
if 5 > 3 then println("Works!") else () // ✅ NO import needed!
}

Common imports:

  • println, printimport std/io (println)
  • readFile, writeFileimport std/fs (readFile, writeFile)
  • now, sleepimport std/clock (now, sleep)
  • httpGet, httpPostimport std/net (httpGet, httpPost)
  • show → builtin, DO NOT IMPORT
  • <, >, <=, >=, ==, !=AUTO-IMPORTED, NO IMPORT NEEDED!

IMPORTANT: If you see error "No instance for Ord" or "No instance for Eq", you may have set AILANG_NO_PRELUDE=1. This is rare - std/prelude is auto-imported by default.

Module Structure

Every AILANG program must be a module with exported functions:

module benchmark/solution

import std/io (println)

export func main() -> () ! {IO} {
println("Hello, World!")
}

IMPORTANT: Always use module benchmark/solution as the module name for benchmark programs.

Functions

-- Simple pure function
export func add(x: int, y: int) -> int {
x + y
}

-- Function with effects (IO, FS)
export func greet(name: string) -> () ! {IO} {
println("Hello, " ++ name)
}

-- Generic function
export func identity[a](x: a) -> a {
x
}

-- Multi-statement body (SEMICOLONS REQUIRED!)
export func compute() -> int {
let x = 10;
let y = 20;
x + y
}

Anonymous Functions

Inline lambda expressions with familiar syntax:

-- Anonymous function literal
let double = func(x: int) -> int { x * 2 }

-- With type inference
let add = func(x, y) { x + y }

-- With effects
let greet = func(name: string) -> () ! {IO} {
println("Hello, " ++ name)
}

-- Higher-order functions
let apply = func(f: func(int) -> int, x: int) -> int { f(x) }
apply(func(n: int) -> int { n * 2 }, 5) -- 10

IMPORTANT: Use => for lambdas (\x. body), use -> { for func expressions.

Recursive Lambdas

The letrec keyword enables recursive function definitions in expressions:

-- Fibonacci using letrec
letrec fib = \n. if n < 2 then n else fib(n - 1) + fib(n - 2) in
fib(10) -- 55

-- Factorial
letrec factorial = \n. if n == 0 then 1 else n * factorial(n - 1) in
factorial(5) -- 120

Use case: Recursive functions in REPL or inside expressions without full function declarations.

Numeric Conversions

Explicit type conversion builtins for int ↔ float:

-- Int to Float
intToFloat(42) -- 42.0

-- Float to Int (truncates towards zero)
floatToInt(3.14) -- 3
floatToInt(-3.14) -- -3

-- Mixed arithmetic (requires conversion)
let result = intToFloat(1) + 2.5 -- 3.5

IMPORTANT: AILANG does NOT do automatic numeric coercion. You MUST use these builtins.

Block Expressions

Blocks allow sequencing statements with semicolons:

export func demo() -> () ! {IO} {
{
println("First");
println("Second");
println("Third")
}
}

-- Blocks are expressions - the last value is returned
export func compute() -> int {
{
let x = 10;
let y = 20;
x + y
}
}

CRITICAL: Semicolons REQUIRED between statements! Missing semicolons will cause parse errors.

Records (with NEW Update Syntax!)

Records are structural types with named fields:

-- Record literal
let person = {name: "Alice", age: 30, city: "NYC"}

-- Field access
person.name -- "Alice"
person.age -- 30

-- Use in functions
export func describe(p: {name: string, age: int}) -> string {
p.name ++ " is " ++ show(p.age)
}

NEW in v0.3.6: Record Update Syntax!

Functional record updates create a new record with specified fields changed:

let person = {name: "Alice", age: 30, city: "NYC"};

-- Update one field
let older = {person | age: 31};
-- Result: {name: "Alice", age: 31, city: "NYC"}

-- Update multiple fields
let moved = {older | city: "SF", age: 32};
-- Result: {name: "Alice", age: 32, city: "SF"}

-- Complex base expressions work
let updated = {getRecord() | field: value};
let nested = {config.server | port: 8080};

IMPORTANT: Updates are immutable - they create NEW records, leaving originals unchanged!

Algebraic Data Types (ADTs)

✅ CORRECT AILANG SYNTAX:

type Option[a] = Some(a) | None
type Result[a, e] = Ok(a) | Err(e)
type List[a] = Cons(a, List[a]) | Nil

export func getOrElse[a](opt: Option[a], default: a) -> a {
match opt {
Some(x) => x,
None => default
}
}

NEW in v0.3.8: Multi-line ADTs with Optional Leading Pipe!

-- Single-line (traditional)
type Tree = Leaf(int) | Node(Tree, int, Tree)

-- Multi-line (NEW - optional leading pipe)
type Tree =
| Leaf(int)
| Node(Tree, int, Tree)

-- Also valid (no leading pipe on first variant)
type Tree =
Leaf(int)
| Node(Tree, int, Tree)

Both styles work identically! Use multi-line for complex ADTs with many variants.

❌ WRONG - This is Rust/other languages, NOT AILANG:

type Option {        // ❌ Wrong - no { } braces
Some(value) // ❌ Wrong - not AILANG syntax
None
}

Option::Some(42) // ❌ Wrong - AILANG has no :: operator
fn divide(a, b) { } // ❌ Wrong - use 'func' not 'fn'

✅ CORRECT - Use constructors directly:

Some(42)             // ✅ Correct - no namespace needed
None // ✅ Correct

Available Imports

std/io - IO operations (requires ! {IO} effect)

import std/io (println, print, readLine)

export func main() -> () ! {IO} {
println("text")
}

std/fs - File operations (requires ! {FS} effect)

import std/fs (readFile, writeFile, exists)

export func readData() -> string ! {FS} {
readFile("data.txt")
}

std/clock - Time operations (requires ! {Clock} effect)

import std/clock (now, sleep)

export func measureTime() -> () ! {IO, Clock} {
let start = now();
sleep(1000); -- Sleep for 1 second (milliseconds)
let end = now();
println("Elapsed: " ++ show(end - start) ++ "ms")
}

std/net - HTTP operations (requires ! {Net} effect)

import std/net (httpGet, httpPost)

export func fetchData() -> () ! {IO, Net} {
let response = httpGet("https://api.example.com/data");
println(response)
}

IMPORTANT:

  • show is a BUILTIN function - do NOT import it from std/io!
  • Clock and Net have security restrictions (no localhost, private IPs, file:// URLs blocked)
  • Run with capabilities: ailang run --caps IO,FS,Clock,Net --entry main file.ail

std/option - Option type

import std/option (Option, Some, None)

Recursion (instead of loops)

Recursion works perfectly in v0.3.8:

export func factorial(n: int) -> int {
if n <= 1
then 1
else n * factorial(n - 1)
}

-- Recursion with IO effects and blocks
export func countdown(n: int) -> () ! {IO} {
if n <= 0 then {
println("Done!")
} else {
println(show(n));
countdown(n - 1)
}
}

Recursion Limitation in REPL

IMPORTANT: Recursive lambdas are NOT supported in let-bindings:

-- ❌ DOES NOT WORK - let bindings cannot be recursive
let fib = \n. if n < 2 then n else fib(n - 1) + fib(n - 2) in fib(10)
-- Error: undefined variable: fib

✅ WORKAROUND: Use func declarations in module files instead:

-- In a .ail file:
module examples/fib
export func fib(n: int) -> int {
if n < 2 then n else fib(n - 1) + fib(n - 2)
}

Why: Module-level func declarations are automatically recursive. The letrec keyword does not exist in AILANG's surface syntax.

Common Mistakes to Avoid

Don't use for/while loops:

for i in [1, 2, 3] { println(i) }  -- ❌ NOT supported

Use recursion:

export func printAll(xs: [int]) -> () ! {IO} {
match xs {
[] => (),
_ => {
println(show(head(xs)));
printAll(tail(xs))
}
}
}

Don't forget semicolons in blocks:

{
println("First")
println("Second") -- ❌ Parse error! Missing semicolon
}

Add semicolons between statements:

{
println("First");
println("Second") -- ✅ Last statement doesn't need semicolon
}

Don't import show:

import std/io (println, show)  -- ❌ show not in std/io

show is a builtin:

import std/io (println)
-- show is available automatically
println(show(42))

Don't forget module and export:

func main() {  -- ❌ Missing 'module' declaration and 'export'
println("hello")
}

Always use module and export:

module benchmark/solution
import std/io (println)

export func main() -> () ! {IO} {
println("hello")
}

Complete Working Examples

Example 1: Safe Division with Option

module benchmark/solution

import std/io (println)

type Option[a] = Some(a) | None

export func safeDivide(a: float, b: float) -> Option[float] {
if b == 0.0
then None
else Some(a / b)
}

export func printResult(result: Option[float]) -> () ! {IO} {
match result {
Some(v) => println("Result: " ++ show(v)),
None => println("Error: Division by zero")
}
}

export func main() -> () ! {IO} {
let r1 = safeDivide(10.0, 2.0);
printResult(r1);
let r2 = safeDivide(10.0, 0.0);
printResult(r2)
}

Example 2: Countdown with Recursion and Blocks

module benchmark/solution

import std/io (println)

export func countdown(n: int) -> () ! {IO} {
if n <= 0 then {
println("Done!")
} else {
println(show(n));
countdown(n - 1)
}
}

export func main() -> () ! {IO} {
countdown(5)
}

Example 3: Records with Update Syntax

module benchmark/solution

import std/io (println)

export func main() -> () ! {IO} {
let alice = {name: "Alice", age: 30, city: "NYC"};

-- Update age (NEW in v0.3.6!)
let older = {alice | age: 31};

-- Update city
let moved = {older | city: "SF"};

println(moved.name ++ ", " ++ show(moved.age) ++ ", " ++ moved.city)
}

Summary

Structure:

  1. Start with module benchmark/solution (REQUIRED for benchmarks!)
  2. Import what you need from stdlib (import std/io (println))
  3. Define exported functions with export func name(params) -> ReturnType { }
  4. Declare effects with ! {IO, FS} when using IO/FS operations
  5. Use recursion instead of loops - AILANG has NO for/while loops!
  6. Use semicolons between statements in blocks

Remember:

  • ✅ Use func NOT fn, function, or def
  • ✅ ADTs use type Name[a] = Cons1(a) | Cons2 syntax (multi-line optional)
  • ✅ NO :: operator - use constructors directly
  • ✅ NO for/while loops - use recursion
  • ✅ Everything is immutable (no var or mutation)
  • ✅ Pattern matching uses => arrows, guards work (if conditions)
  • ✅ Semicolons REQUIRED between statements in blocks
  • show is builtin - do NOT import it
  • ✅ Records: literals, field access, AND update syntax {r | field: val}
  • ✅ Effects must be declared: ! {IO}, ! {FS}, ! {Clock}, ! {Net}, or combinations
  • ✅ Modulo operator % works: 5 % 3 returns 2
  • ✅ Float comparison works: 0.0 == 0.0 returns true
  • ✅ std/prelude AUTO-IMPORTED: No need to import Ord/Eq for comparisons!
  • ✅ Four effects available: IO (console), FS (files), Clock (time), Net (HTTP)

If you're not sure, look at the examples above! They show the exact AILANG syntax.

v0.3.8 Release Notes (October 2025):

  • Multi-line ADTs: Optional leading pipe type Tree = | Leaf | Node for better readability
  • Operator lowering fix: Division operators now resolve correctly (fixes runtime errors)
  • Benchmark improvements: 49.1% success rate (up from 38.6% in v0.3.7) - +10.5% improvement!
  • Test coverage: 28.9% (improving steadily)
  • All previous features: auto-import std/prelude, record updates, Clock/Net effects, numeric conversions