Files
lux/docs/guide/07-modules.md
Brandon Lucas 44f88afcf8 docs: add comprehensive language documentation
Documentation structure inspired by Rust Book, Elm Guide, and others:

Guide (10 chapters):
- Introduction and setup
- Basic types (Int, String, Bool, List, Option, Result)
- Functions (closures, higher-order, composition)
- Data types (ADTs, pattern matching, records)
- Effects (the core innovation)
- Handlers (patterns and techniques)
- Modules (imports, exports, organization)
- Error handling (Fail, Option, Result)
- Standard library reference
- Advanced topics (traits, generics, optimization)

Reference:
- Complete syntax reference

Tutorials:
- Calculator (parsing, evaluation, REPL)
- Dependency injection (testing with effects)
- Project ideas (16 projects by difficulty)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 17:43:41 -05:00

6.3 KiB

Chapter 7: Modules

As programs grow, you need to split code across files. Lux has a module system for organizing and sharing code.

Module Basics

Every .lux file is a module. The file path determines the module path:

project/
├── main.lux           # Module: main
├── utils.lux          # Module: utils
└── lib/
    ├── math.lux       # Module: lib/math
    └── strings.lux    # Module: lib/strings

Importing Modules

Basic Import

import lib/math

fn main(): Unit with {Console} = {
    Console.print(toString(lib/math.square(5)))
}

Wait, that's verbose. Use an alias:

Aliased Import

import lib/math as math

fn main(): Unit with {Console} = {
    Console.print(toString(math.square(5)))
}

Selective Import

Import specific items directly:

import lib/math.{square, cube}

fn main(): Unit with {Console} = {
    Console.print(toString(square(5)))   // No prefix needed
    Console.print(toString(cube(3)))
}

Wildcard Import

Import everything:

import lib/math.*

fn main(): Unit with {Console} = {
    Console.print(toString(square(5)))
    Console.print(toString(cube(3)))
    Console.print(toString(factorial(6)))
}

Use sparingly—it can cause name conflicts.

Visibility

By default, declarations are private. Use pub to export:

// lib/math.lux

// Public - can be imported
pub fn square(x: Int): Int = x * x

pub fn cube(x: Int): Int = x * x * x

// Private - internal helper
fn helper(x: Int): Int = x + 1

// Public type
pub type Point = { x: Int, y: Int }

Creating a Module

Let's create a string utilities module:

// lib/strings.lux

/// Repeat a string n times
pub fn repeat(s: String, n: Int): String =
    if n <= 0 then ""
    else s + repeat(s, n - 1)

/// Check if string starts with prefix
pub fn startsWith(s: String, prefix: String): Bool =
    String.startsWith(s, prefix)

/// Check if string ends with suffix
pub fn endsWith(s: String, suffix: String): Bool =
    String.endsWith(s, suffix)

/// Pad string on the left to reach target length
pub fn padLeft(s: String, length: Int, char: String): String = {
    let current = String.length(s)
    if current >= length then s
    else padLeft(char + s, length, char)
}

/// Pad string on the right to reach target length
pub fn padRight(s: String, length: Int, char: String): String = {
    let current = String.length(s)
    if current >= length then s
    else padRight(s + char, length, char)
}

Using it:

// main.lux
import lib/strings as str

fn main(): Unit with {Console} = {
    Console.print(str.repeat("ab", 3))       // "ababab"
    Console.print(str.padLeft("5", 3, "0"))  // "005"
}

let output = run main() with {}

Module Organization Patterns

Feature Modules

Group by feature:

project/
├── main.lux
├── users/
│   ├── types.lux      # User type definitions
│   ├── repository.lux # Database operations
│   └── service.lux    # Business logic
├── orders/
│   ├── types.lux
│   ├── repository.lux
│   └── service.lux
└── shared/
    ├── utils.lux
    └── effects.lux

Layer Modules

Group by layer:

project/
├── main.lux
├── domain/           # Business logic (pure)
│   ├── user.lux
│   └── order.lux
├── effects/          # Effect definitions
│   ├── database.lux
│   └── email.lux
├── handlers/         # Effect implementations
│   ├── postgres.lux
│   └── smtp.lux
└── api/              # Entry points
    └── http.lux

Standard Library

Lux has a standard library in the std/ directory:

import std/prelude.*    // Common utilities
import std/option       // Option helpers
import std/result       // Result helpers
import std/io           // I/O utilities

std/prelude

import std/prelude.*

identity(42)           // 42
compose(f, g)          // Function composition
not(true)              // false
and(true, false)       // false
or(true, false)        // true

std/option

import std/option as opt

opt.some(42)                    // Some(42)
opt.none()                      // None
opt.map(Some(5), double)        // Some(10)
opt.flatMap(Some(5), safeDivide)
opt.unwrapOr(None, 0)           // 0

std/result

import std/result as res

res.ok(42)                      // Ok(42)
res.err("oops")                 // Err("oops")
res.mapOk(Ok(5), double)        // Ok(10)
res.mapErr(Err("x"), upper)     // Err("X")

Circular Dependencies

Lux detects circular imports:

// a.lux
import b
pub fn fromA(): Int = b.fromB() + 1

// b.lux
import a
pub fn fromB(): Int = a.fromA() + 1

// Error: Circular dependency detected

Solution: extract shared code to a third module.

Module Best Practices

1. One Concept Per Module

// Good: focused module
// user.lux - User type and operations
pub type User = { id: Int, name: String }
pub fn createUser(name: String): User = ...
pub fn validateUser(u: User): Bool = ...

// Bad: kitchen sink
// utils.lux - random stuff
pub fn parseUser(s: String): User = ...
pub fn formatDate(d: Date): String = ...
pub fn calculateTax(amount: Int): Int = ...

2. Export Deliberately

// Only export what others need
pub fn publicApi(): Result = ...

// Keep helpers private
fn internalHelper(): Int = ...

3. Use Aliases for Clarity

// Clear what comes from where
import database/postgres as db
import cache/redis as cache

fn getData(id: Int): Data with {Database, Cache} = {
    match cache.get(id) {
        Some(d) => d,
        None => {
            let d = db.query(id)
            cache.set(id, d)
            d
        }
    }
}
// Standard library
import std/prelude.*
import std/option as opt

// Project modules
import lib/database as db
import lib/cache as cache

// Local modules
import ./types.{User, Order}
import ./validation

Summary

Syntax Meaning
import path/to/module Import module
import path/to/module as alias Import with alias
import path/to/module.{a, b} Import specific items
import path/to/module.* Import all exports
pub fn / pub type Export declaration

Next

Chapter 8: Error Handling - Handling failures gracefully.