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>
321 lines
6.3 KiB
Markdown
321 lines
6.3 KiB
Markdown
# 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
|
|
|
|
```lux
|
|
import lib/math
|
|
|
|
fn main(): Unit with {Console} = {
|
|
Console.print(toString(lib/math.square(5)))
|
|
}
|
|
```
|
|
|
|
Wait, that's verbose. Use an alias:
|
|
|
|
### Aliased Import
|
|
|
|
```lux
|
|
import lib/math as math
|
|
|
|
fn main(): Unit with {Console} = {
|
|
Console.print(toString(math.square(5)))
|
|
}
|
|
```
|
|
|
|
### Selective Import
|
|
|
|
Import specific items directly:
|
|
|
|
```lux
|
|
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:
|
|
|
|
```lux
|
|
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:
|
|
|
|
```lux
|
|
// 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:
|
|
|
|
```lux
|
|
// 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:
|
|
|
|
```lux
|
|
// 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:
|
|
|
|
```lux
|
|
import std/prelude.* // Common utilities
|
|
import std/option // Option helpers
|
|
import std/result // Result helpers
|
|
import std/io // I/O utilities
|
|
```
|
|
|
|
### std/prelude
|
|
|
|
```lux
|
|
import std/prelude.*
|
|
|
|
identity(42) // 42
|
|
compose(f, g) // Function composition
|
|
not(true) // false
|
|
and(true, false) // false
|
|
or(true, false) // true
|
|
```
|
|
|
|
### std/option
|
|
|
|
```lux
|
|
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
|
|
|
|
```lux
|
|
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:
|
|
|
|
```lux
|
|
// 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
|
|
|
|
```lux
|
|
// 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
|
|
|
|
```lux
|
|
// Only export what others need
|
|
pub fn publicApi(): Result = ...
|
|
|
|
// Keep helpers private
|
|
fn internalHelper(): Int = ...
|
|
```
|
|
|
|
### 3. Use Aliases for Clarity
|
|
|
|
```lux
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Group Related Imports
|
|
|
|
```lux
|
|
// 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](08-errors.md) - Handling failures gracefully.
|