Skip to main content

Effect System

AILANG uses an algebraic effect system with capability-based security. All side effects must be declared in function signatures and granted at runtime.

Why Effects?

Traditional languages hide side effects:

# Python - side effects are invisible
def process_data():
data = read_file("input.txt") # FS access hidden
print("Processing...") # IO hidden
return transform(data)

AILANG makes effects explicit:

-- AILANG - effects declared in signature
func processData() -> Data ! {IO, FS} {
println("Processing...");
let data = readFile("input.txt");
transform(data)
}

Benefits:

  • Reasoning: Know exactly what a function can do from its type
  • Security: Grant only needed capabilities at runtime
  • Testing: Mock effects for deterministic testing
  • AI-Friendly: Models can verify effect safety statically

Available Effects

IO Effect

Console input/output operations.

import std/io (println, print, readLine)

func greet(name: string) -> () ! {IO} {
print("Hello, ");
println(name ++ "!")
}

Builtins:

FunctionTypeDescription
printlnstring -> () ! {IO}Print with newline
printstring -> () ! {IO}Print without newline
readLine() -> string ! {IO}Read line from stdin

FS Effect

File system operations.

import std/fs (readFile, writeFile, exists)

func copyFile(src: string, dst: string) -> () ! {FS} {
let content = readFile(src);
writeFile(dst, content)
}

Builtins:

FunctionTypeDescription
readFilestring -> string ! {FS}Read file contents
writeFile(string, string) -> () ! {FS}Write content to file
existsstring -> bool ! {FS}Check if file exists

Clock Effect

Time operations with deterministic mode support.

import std/clock (now, sleep)

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

Builtins:

FunctionTypeDescription
now() -> int ! {Clock}Current monotonic time (ms)
sleepint -> () ! {Clock}Sleep for milliseconds

Deterministic Mode:

# Fixed time for reproducible tests
ailang run --clock-mode fixed --clock-start 1000 program.ail

Net Effect

HTTP operations with security protections.

import std/net (httpGet, httpPost)

func fetchData(url: string) -> string ! {Net} {
httpGet(url)
}

func postData(url: string, body: string) -> string ! {Net} {
httpPost(url, body)
}

Builtins:

FunctionTypeDescription
httpGetstring -> string ! {Net}HTTP GET request
httpPost(string, string) -> string ! {Net}HTTP POST request

Security Features:

  • DNS rebinding prevention
  • Private IP blocking (10.x.x.x, 192.168.x.x, etc.)
  • HTTPS enforcement (configurable)

Env Effect

Environment variable access.

import std/env (getEnv)

func getConfig() -> string ! {Env} {
match getEnv("CONFIG_PATH") {
Some(path) => path,
None => "/etc/default.conf"
}
}

Builtins:

FunctionTypeDescription
getEnvstring -> Option[string] ! {Env}Get environment variable

Effect Syntax

Declaring Effects

Effects are declared after the return type with !:

-- Single effect
func greet() -> () ! {IO} { ... }

-- Multiple effects
func process() -> Data ! {IO, FS, Net} { ... }

-- Pure function (no effects)
func add(x: int, y: int) -> int { x + y }

-- Explicit pure annotation
pure func factorial(n: int) -> int { ... }

Combining Effects

Effects from called functions are combined:

func helper() -> () ! {IO} {
println("Helper called")
}

func main() -> () ! {IO, FS} {
helper(); -- IO effect flows up
let x = readFile("data.txt"); -- FS effect
println(x) -- IO effect
}

Effect Subsumption

Functions can be called in contexts with additional effects:

func pureCalc(x: int) -> int { x * 2 }

func effectful() -> int ! {IO} {
println("Calculating...");
pureCalc(21) -- Pure function called in effectful context
}

Running with Capabilities

Effects require explicit capability grants at runtime:

# Grant IO capability
ailang run --caps IO --entry main program.ail

# Grant multiple capabilities
ailang run --caps IO,FS,Net --entry main program.ail

# Grant all capabilities (development only!)
ailang run --caps ALL --entry main program.ail

Missing capability error:

Error: Function 'main' requires capability IO but it was not granted.
Use --caps IO to grant this capability.

Effect Safety

Static Checking

The type checker verifies effect declarations:

-- Error: readFile requires FS but not declared
func badFunction() -> string ! {IO} {
readFile("data.txt") -- Compile error!
}

Runtime Enforcement

Even if you declare an effect, you must grant it:

func main() -> () ! {FS} {
writeFile("output.txt", "data")
}
# Fails - FS not granted
ailang run --caps IO --entry main program.ail
# Error: Capability FS required but not granted

Effect Patterns

Effect Polymorphism

Functions can work with any effect set:

-- Works with any effects
func twice[e](f: () -> () ! e) -> () ! e {
f();
f()
}

-- Usage
twice(\(). println("Hello")) -- ! {IO}
twice(\(). writeFile("log", "entry")) -- ! {FS}

Conditional Effects

Use pattern matching to handle effect boundaries:

func maybeLog(verbose: bool, msg: string) -> () ! {IO} {
if verbose then println(msg) else ()
}

Effect-Free Core

Keep business logic pure, effects at boundaries:

-- Pure business logic
pure func calculateTax(amount: float) -> float {
amount * 0.2
}

-- Effectful boundary
func processInvoice(id: string) -> () ! {IO, FS} {
let data = readFile("invoices/" ++ id ++ ".json");
let amount = parseAmount(data);
let tax = calculateTax(amount); -- Pure!
println("Tax: " ++ show(tax))
}

Testing with Effects

Mocking Effects (Planned)

-- Future: Mock effects for testing
test "file processing" with {
FS.mock({
"input.txt" => "test data"
})
} {
let result = processFile("input.txt");
assert(result == "TEST DATA")
}

Pure Function Testing (Current)

Use inline tests for pure functions:

pure func double(x: int) -> int
tests [
(0, 0),
(5, 10),
((-3), (-6))
]
{
x * 2
}