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
Recommended Structure
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])))
}
Related Resources
- Language Syntax - Complete syntax reference
- Effect System - Capabilities and effects
- Getting Started - Installation and first program
- Module Execution - Running modules