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>
This commit is contained in:
2026-02-13 17:43:41 -05:00
parent 9ee7148d24
commit 44f88afcf8
16 changed files with 4845 additions and 0 deletions

350
docs/guide/05-effects.md Normal file
View File

@@ -0,0 +1,350 @@
# Chapter 5: Effects
This is where Lux gets interesting. Effects are the core innovation—they make side effects explicit, controllable, and composable.
## The Problem with Side Effects
In most languages, functions can do *anything*:
```javascript
// JavaScript - what does this do?
function process(x) {
return x * 2;
}
```
It looks pure, but it could:
- Print to console
- Write to a file
- Make HTTP requests
- Throw exceptions
- Modify global state
- Launch missiles
You can't tell from the signature. You have to read the implementation.
## The Lux Solution
In Lux, effects are declared:
```lux
// Pure - only computes
fn double(x: Int): Int = x * 2
// Uses Console - you can see it
fn printDouble(x: Int): Unit with {Console} =
Console.print(toString(x * 2))
```
The `with {Console}` tells you this function interacts with the console. It's part of the type.
## Built-in Effects
Lux provides several built-in effects:
| Effect | Operations | Purpose |
|--------|------------|---------|
| `Console` | `print`, `readLine`, `readInt` | Terminal I/O |
| `Fail` | `fail` | Early termination |
| `State` | `get`, `put` | Mutable state |
| `Random` | `int`, `float`, `bool` | Random numbers |
| `File` | `read`, `write`, `exists` | File system |
| `Process` | `exec`, `env`, `args` | System processes |
| `Http` | `get`, `post`, `put`, `delete` | HTTP client |
Example usage:
```lux
fn main(): Unit with {Console, Random} = {
let n = Random.int(1, 100)
Console.print("Random number: " + toString(n))
}
let output = run main() with {}
```
## Effect Propagation
Effects propagate up the call stack:
```lux
fn helper(): Int with {Console} = {
Console.print("In helper")
42
}
// Must declare Console because it calls helper
fn caller(): Int with {Console} = {
let x = helper()
x * 2
}
// Error: caller uses Console but doesn't declare it
fn broken(): Int = {
caller() // Error!
}
```
The rule: if you call a function with effect E, you must either:
1. Declare E in your signature
2. Handle E with a `run ... with {}` block
## Running Effects
The `run ... with {}` block executes effectful code:
```lux
fn greet(): Unit with {Console} =
Console.print("Hello!")
// Execute with default handlers
let result = run greet() with {}
```
For built-in effects, `with {}` uses the default implementations (real console, real files, etc.).
## Custom Effects
You can define your own effects:
```lux
effect Logger {
fn log(level: String, message: String): Unit
fn getLevel(): String
}
```
This declares an effect with two operations. To use it:
```lux
fn processData(data: Int): Int with {Logger} = {
Logger.log("info", "Starting processing")
let result = data * 2
Logger.log("debug", "Result: " + toString(result))
result
}
```
But this won't run yet—we need a *handler*.
## Handlers
Handlers define how effect operations behave:
```lux
handler consoleLogger: Logger {
fn log(level, message) = {
Console.print("[" + level + "] " + message)
resume(())
}
fn getLevel() = resume("debug")
}
```
Key concept: **`resume(value)`** continues the computation with `value` as the result of the effect operation.
Now we can run:
```lux
fn main(): Unit with {Console} = {
let result = run processData(21) with {
Logger = consoleLogger
}
Console.print("Final: " + toString(result))
}
let output = run main() with {}
```
Output:
```
[info] Starting processing
[debug] Result: 42
Final: 42
```
## The Power of Handlers
### Different Implementations
Same code, different behaviors:
```lux
// Console logging
handler consoleLogger: Logger {
fn log(level, msg) = {
Console.print("[" + level + "] " + msg)
resume(())
}
fn getLevel() = resume("debug")
}
// Silent (for testing)
handler silentLogger: Logger {
fn log(level, msg) = resume(())
fn getLevel() = resume("none")
}
// Collecting logs
handler collectingLogger: Logger {
fn log(level, msg) = {
State.put(State.get() + "[" + level + "] " + msg + "\n")
resume(())
}
fn getLevel() = resume("all")
}
```
### Resumable Operations
Unlike exceptions, handlers can *continue* the computation:
```lux
effect Ask {
fn ask(prompt: String): String
}
fn survey(): String with {Ask} = {
let name = Ask.ask("Name?")
let age = Ask.ask("Age?")
name + " is " + age + " years old"
}
// Handler that provides answers
handler mockAnswers: Ask {
fn ask(prompt) =
if String.contains(prompt, "Name") then resume("Alice")
else resume("30")
}
run survey() with { Ask = mockAnswers }
// Returns: "Alice is 30 years old"
```
The computation pauses at `Ask.ask`, the handler provides a value, and `resume` continues from where it left off.
## Effect Composition
Multiple effects combine naturally:
```lux
fn program(): Int with {Console, Random, Logger} = {
Logger.log("info", "Starting")
let n = Random.int(1, 10)
Console.print("Got: " + toString(n))
Logger.log("debug", "Returning " + toString(n))
n
}
let result = run program() with {
Logger = consoleLogger
}
```
No monad transformers, no lifting, no complexity. Effects just work together.
## Why This Matters
### 1. Testability
```lux
// Production code
fn fetchUser(id: Int): String with {Http} =
Http.get("https://api.example.com/users/" + toString(id))
// Test with mock HTTP
handler mockHttp: Http {
fn get(url) = resume("{\"name\": \"Test User\"}")
// ... other operations
}
// Test runs without network
let result = run fetchUser(1) with { Http = mockHttp }
```
### 2. Explicit Dependencies
```lux
// You can see exactly what this function needs
fn processOrder(order: Order): Receipt with {Database, Logger, Email}
```
### 3. Controlled Side Effects
```lux
// This function is pure - it CAN'T do I/O
fn calculateTotal(items: List<Item>): Int =
List.fold(items, 0, fn(acc: Int, item: Item): Int => acc + item.price)
// Compile error if you try to add Console.print here
```
## Common Patterns
### Dependency Injection
```lux
effect Database {
fn query(sql: String): List<Row>
fn execute(sql: String): Int
}
fn getUsers(): List<User> with {Database} =
Database.query("SELECT * FROM users")
// Production
handler postgresDb: Database { /* real implementation */ }
// Testing
handler mockDb: Database {
fn query(sql) = resume([mockRow1, mockRow2])
fn execute(sql) = resume(1)
}
```
### Configuration
```lux
effect Config {
fn get(key: String): String
}
fn appUrl(): String with {Config} =
Config.get("APP_URL")
handler envConfig: Config {
fn get(key) = resume(Process.env(key))
}
handler testConfig: Config {
fn get(key) = resume("http://localhost:8080")
}
```
### Early Return
```lux
fn validateUser(user: User): User with {Fail} = {
if user.name == "" then Fail.fail("Name required")
else if user.age < 0 then Fail.fail("Invalid age")
else user
}
// Fail stops execution
let result = run validateUser(invalidUser) with {}
```
## Summary
| Concept | Syntax |
|---------|--------|
| Declare effect | `effect Name { fn op(): Type }` |
| Use effect | `fn f(): T with {Effect}` |
| Effect operation | `Effect.operation(args)` |
| Define handler | `handler name: Effect { fn op(...) = ... }` |
| Resume | `resume(value)` |
| Run with handler | `run expr with { Effect = handler }` |
## Next
[Chapter 6: Handlers](06-handlers.md) - Deep dive into handler patterns.