Skip to main content

Module System

AILANG uses a module system for code organization, with explicit imports and exports for clear dependencies.

Module Declaration

Every AILANG file should declare its module path:

module examples/math

-- Module contents follow...

The module path should match the file path relative to the project root:

  • File: examples/math.ail
  • Module: module examples/math

Relaxed Module Matching

For quick prototyping, you can disable strict path matching:

# Flag
ailang run --relax-modules --caps IO --entry main temp.ail

# Environment variable
AILANG_RELAX_MODULES=1 ailang run --caps IO --entry main temp.ail

Files in temp directories (/tmp/, /var/folders/) auto-relax with a warning.

Imports

Basic Imports

Import specific symbols from a module:

import std/io (println, print)
import std/fs (readFile, writeFile)

func main() -> () ! {IO, FS} {
println("Reading file...");
let content = readFile("data.txt");
println(content)
}

Import Aliasing (v0.5.1+)

Rename modules on import to avoid conflicts:

-- Module alias: qualified access
import std/list as List

func main() -> () ! {IO} {
let xs = [1, 2, 3];
println(show(List.length(xs))); -- 3
println(show(List.map(\x. x * 2, xs))) -- [2, 4, 6]
}

Symbol Aliasing (v0.5.1+)

Rename individual symbols on import:

-- Symbol alias: direct access with new name
import std/list (length as listLength, map as listMap)

func main() -> () ! {IO} {
let xs = [1, 2, 3];
println(show(listLength(xs))); -- 3
println(show(listMap(\x. x * 2, xs))) -- [2, 4, 6]
}

Combined Aliasing

Use both module and symbol aliasing together:

-- Module alias + specific symbols
import std/list as L (map, filter)

func main() -> () ! {IO} {
let xs = [1, 2, 3, 4, 5];

-- Direct access to map and filter
let doubled = map(\x. x * 2, xs);
let evens = filter(\x. x % 2 == 0, doubled);

-- Qualified access to other functions
println(show(L.length(evens))) -- 2
}

Resolving Name Conflicts

When two modules export the same name:

-- Both modules have a 'parse' function
import json/parser as JsonParser
import xml/parser as XmlParser

func parseInput(format: string, data: string) -> Result[Data] {
match format {
"json" => JsonParser.parse(data),
"xml" => XmlParser.parse(data),
_ => Err("Unknown format")
}
}

Exports

Exporting Functions

Use export to make functions available to other modules:

module math/utils

-- Exported: available to importers
export func add(x: int, y: int) -> int {
x + y
}

-- Not exported: private to this module
func helper(x: int) -> int {
x * 2
}

-- Can use private functions internally
export func double(x: int) -> int {
helper(x)
}

Exporting Types

Export type definitions:

module data/option

-- Export type and constructors
export type Option[a] = Some(a) | None

-- Export functions that work with the type
export func map[a, b](f: a -> b, opt: Option[a]) -> Option[b] {
match opt {
Some(x) => Some(f(x)),
None => None
}
}

Re-Exporting

Import and immediately re-export:

module prelude

-- Re-export common utilities
export import std/io (println, print)
export import std/list (map, filter, fold)
export import std/option (Option, Some, None)

Standard Library

AILANG includes a standard library with common utilities:

std/io

Console I/O operations.

import std/io (println, print, readLine)

func main() -> () ! {IO} {
print("Enter name: ");
let name = readLine();
println("Hello, " ++ name ++ "!")
}

std/fs

File system operations.

import std/fs (readFile, writeFile, exists)

func main() -> () ! {FS} {
if exists("config.json") then
let config = readFile("config.json");
writeFile("config.backup.json", config)
else
writeFile("config.json", "{}")
}

std/list

List operations.

import std/list (map, filter, fold, length, head, tail)

func main() -> () ! {IO} {
let numbers = [1, 2, 3, 4, 5];

let doubled = map(\x. x * 2, numbers);
let evens = filter(\x. x % 2 == 0, doubled);
let sum = fold(\acc x. acc + x, 0, evens);

println("Sum of doubled evens: " ++ show(sum))
}

std/prelude

Common utilities automatically available:

  • Type classes: Num, Eq, Ord, Show
  • Operators: +, -, *, /, ==, <, >, ++
  • Functions: show, compare

Entry Points

Main Function

Modules can define entry points:

module examples/hello

import std/io (println)

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

Run with:

ailang run --caps IO --entry main examples/hello.ail

Custom Entry Points

Any exported function can be an entry point:

module examples/calc

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

export func main() -> () ! {IO} {
println(show(factorial(10)))
}
# Run main
ailang run --caps IO --entry main examples/calc.ail

# Run factorial directly (pure function, no caps needed)
ailang run --entry factorial examples/calc.ail

Module Organization

project/
├── src/
│ ├── main.ail -- module src/main
│ ├── utils/
│ │ ├── math.ail -- module src/utils/math
│ │ └── strings.ail -- module src/utils/strings
│ └── data/
│ ├── types.ail -- module src/data/types
│ └── json.ail -- module src/data/json
├── tests/
│ └── test_math.ail -- module tests/test_math
└── examples/
└── demo.ail -- module examples/demo

Circular Dependencies

AILANG does not allow circular imports:

-- module a
import b (funcB) -- OK
export func funcA() = funcB()

-- module b
import a (funcA) -- ERROR: Circular dependency!
export func funcB() = funcA()

Solution: Extract shared code to a third module.

REPL vs File Modules

REPL Mode

In the REPL, you can use imports interactively:

ailang> import std/list (map)
ailang> map(\x. x * 2, [1, 2, 3])
[2, 4, 6]

File Mode

Files require module declarations:

module scratch/test

import std/list (map)

export func main() -> () ! {IO} {
println(show(map(\x. x * 2, [1, 2, 3])))
}