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

View File

@@ -0,0 +1,222 @@
# Chapter 1: Introduction to Lux
Welcome to Lux, a functional programming language where side effects are first-class citizens.
## Why Lux?
Every program does more than compute. It reads files, makes network requests, prints output, handles errors. In most languages, these *effects* are invisible—a function's type doesn't tell you what it might do.
Lux changes this. Effects are:
- **Visible** in the type signature
- **Controllable** via handlers
- **Composable** without boilerplate
This isn't just academic. It means:
- Tests can swap real I/O for mocks—automatically
- Error handling is explicit, not exceptional
- Dependencies are injected through the type system
## Installation
### From Source (Cargo)
```bash
git clone https://github.com/your-org/lux
cd lux
cargo build --release
# Add to PATH
export PATH="$PATH:$(pwd)/target/release"
```
### With Nix
```bash
# Enter development shell
nix develop
# Or build
nix build
./result/bin/lux
```
### Verify Installation
```bash
$ lux --version
Lux 0.1.0
$ lux --help
Usage: lux [OPTIONS] [FILE]
Options:
-c, --check Type check without running
--lsp Start LSP server
--repl Start interactive REPL
-h, --help Print help
```
## Your First Program
Create a file called `hello.lux`:
```lux
// hello.lux - Your first Lux program
fn main(): Unit with {Console} =
Console.print("Hello, Lux!")
let output = run main() with {}
```
Run it:
```bash
$ lux hello.lux
Hello, Lux!
```
Let's break this down:
### The Function Signature
```lux
fn main(): Unit with {Console}
```
- `fn main()` - A function named `main` with no parameters
- `: Unit` - Returns `Unit` (like `void`)
- `with {Console}` - **Uses the Console effect**
That last part is key. The type tells us this function prints to the console. A function without `with {...}` is *pure*—it can only compute.
### The Body
```lux
Console.print("Hello, Lux!")
```
`Console.print` is an *effect operation*. It's not a regular function—it's a request to the environment to print something.
### Running Effects
```lux
let output = run main() with {}
```
The `run ... with {}` expression executes a computation with its effects. The `{}` means "use the default handlers for all effects." Console's default handler prints to stdout.
## The REPL
Lux has an interactive mode:
```bash
$ lux
Lux v0.1.0 - Type :help for commands
>
```
Try some expressions:
```
> 1 + 2
3
> "Hello, " + "World"
"Hello, World"
> [1, 2, 3]
[1, 2, 3]
> List.map([1, 2, 3], fn(x: Int): Int => x * 2)
[2, 4, 6]
```
Define functions:
```
> fn square(x: Int): Int = x * x
<function>
> square(5)
25
> fn greet(name: String): Unit with {Console} = Console.print("Hi, " + name)
<function>
> run greet("Alice") with {}
Hi, Alice
```
REPL commands:
```
:help - Show help
:type e - Show type of expression
:quit - Exit REPL
:clear - Clear screen
```
## A Slightly Bigger Program
Let's write a program that asks for your name:
```lux
// greet.lux
fn askName(): String with {Console} = {
Console.print("What's your name?")
Console.readLine()
}
fn main(): Unit with {Console} = {
let name = askName()
Console.print("Hello, " + name + "!")
}
let output = run main() with {}
```
```bash
$ lux greet.lux
What's your name?
> Alice
Hello, Alice!
```
Notice how:
- `askName` returns `String` but has `with {Console}` because it does I/O
- Both functions declare their effects
- The `run` at the end provides the runtime
## Pure vs Effectful
Here's the key insight. Compare:
```lux
// Pure - no effects, only computes
fn add(a: Int, b: Int): Int = a + b
// Effectful - uses Console
fn printSum(a: Int, b: Int): Unit with {Console} =
Console.print(toString(a + b))
```
You can call `add` from anywhere. But `printSum` can only be called from:
1. Another function that declares `Console`
2. Inside a `run ... with {}` block
This is the effect discipline. Effects propagate up until handled.
## What's Next
Now that you can run programs, let's learn:
- [Chapter 2: Basic Types](02-basic-types.md) - Numbers, strings, booleans
- [Chapter 3: Functions](03-functions.md) - Definitions, closures, composition
- [Chapter 4: Data Types](04-data-types.md) - ADTs and pattern matching
Or jump ahead to what makes Lux special:
- [Chapter 5: Effects](05-effects.md) - The core innovation

View File

@@ -0,0 +1,248 @@
# Chapter 2: Basic Types
Lux is statically typed with full type inference. You rarely need to write types—the compiler figures them out—but understanding them helps.
## Primitive Types
### Int
64-bit signed integers:
```lux
let x = 42
let y = -17
let big = 9_223_372_036_854_775_807 // Underscores for readability
// Arithmetic
x + y // 25
x - y // 59
x * y // -714
x / y // -2 (integer division)
x % y // 8 (remainder)
```
### Float
64-bit floating point (IEEE 754):
```lux
let pi = 3.14159
let e = 2.718
pi * 2.0 // 6.28318
e / 2.0 // 1.359
```
### Bool
Boolean values:
```lux
let yes = true
let no = false
// Operators
true && false // false (and)
true || false // true (or)
!true // false (not)
// Comparison
5 > 3 // true
5 == 5 // true
5 != 3 // true
5 <= 5 // true
```
### String
UTF-8 strings:
```lux
let greeting = "Hello"
let name = "World"
// Concatenation
greeting + ", " + name + "!" // "Hello, World!"
// String interpolation
let message = "The answer is ${40 + 2}" // "The answer is 42"
// Multiline
let poem = "
Roses are red,
Violets are blue,
Lux has effects,
And so can you.
"
```
### Char
Single Unicode characters:
```lux
let letter = 'A'
let emoji = '🎉'
```
### Unit
The type with only one value, `()`. Used when a function doesn't return anything meaningful:
```lux
fn printHello(): Unit with {Console} =
Console.print("Hello")
let nothing: Unit = ()
```
## Type Annotations
Usually optional, but sometimes helpful:
```lux
// Inferred
let x = 42 // Int
let s = "hello" // String
// Explicit
let x: Int = 42
let s: String = "hello"
// Required when ambiguous
let empty: List<Int> = []
```
## Lists
Ordered collections of the same type:
```lux
let numbers = [1, 2, 3, 4, 5]
let words = ["hello", "world"]
let empty: List<Int> = []
// Operations (from List module)
List.length(numbers) // 5
List.head(numbers) // Some(1)
List.tail(numbers) // [2, 3, 4, 5]
List.map(numbers, fn(x: Int): Int => x * 2) // [2, 4, 6, 8, 10]
List.filter(numbers, fn(x: Int): Bool => x > 2) // [3, 4, 5]
List.fold(numbers, 0, fn(acc: Int, x: Int): Int => acc + x) // 15
```
## Option
For values that might not exist:
```lux
let some_value: Option<Int> = Some(42)
let no_value: Option<Int> = None
// Pattern matching
fn describe(opt: Option<Int>): String =
match opt {
Some(n) => "Got: " + toString(n),
None => "Nothing"
}
// Option operations
Option.map(Some(5), fn(x: Int): Int => x * 2) // Some(10)
Option.map(None, fn(x: Int): Int => x * 2) // None
Option.getOrElse(Some(5), 0) // 5
Option.getOrElse(None, 0) // 0
```
## Result
For operations that can fail:
```lux
let success: Result<Int, String> = Ok(42)
let failure: Result<Int, String> = Err("Something went wrong")
// Pattern matching
fn handle(r: Result<Int, String>): String =
match r {
Ok(n) => "Success: " + toString(n),
Err(e) => "Error: " + e
}
// Result operations
Result.map(Ok(5), fn(x: Int): Int => x * 2) // Ok(10)
Result.map(Err("oops"), fn(x: Int): Int => x * 2) // Err("oops")
```
## Tuples
Fixed-size collections of different types:
```lux
let pair = (1, "hello")
let triple = (true, 42, "world")
// Access by pattern matching
let (x, y) = pair
// x = 1, y = "hello"
```
## Records
Named fields:
```lux
let person = { name: "Alice", age: 30 }
// Access
person.name // "Alice"
person.age // 30
// With type annotation
type Person = { name: String, age: Int }
let bob: Person = { name: "Bob", age: 25 }
```
## Type Conversion
```lux
// To String
toString(42) // "42"
toString(true) // "true"
toString([1,2,3]) // "[1, 2, 3]"
// String to Int (via Console effect)
let n = Console.readInt()
```
## Type Checking
The compiler catches type errors:
```lux
let x: Int = "hello" // Error: expected Int, got String
fn add(a: Int, b: Int): Int = a + b
add(1, "two") // Error: expected Int, got String
let nums = [1, 2, "three"] // Error: list elements must have same type
```
## Summary
| Type | Example | Description |
|------|---------|-------------|
| `Int` | `42` | 64-bit integer |
| `Float` | `3.14` | 64-bit float |
| `Bool` | `true` | Boolean |
| `String` | `"hello"` | UTF-8 string |
| `Char` | `'A'` | Unicode character |
| `Unit` | `()` | No meaningful value |
| `List<T>` | `[1, 2, 3]` | Ordered collection |
| `Option<T>` | `Some(42)` | Optional value |
| `Result<T, E>` | `Ok(42)` | Success or failure |
| `(A, B)` | `(1, "hi")` | Tuple |
| `{...}` | `{x: 1}` | Record |
## Next
[Chapter 3: Functions](03-functions.md) - Learn to define and compose functions.

272
docs/guide/03-functions.md Normal file
View File

@@ -0,0 +1,272 @@
# Chapter 3: Functions
Functions are the building blocks of Lux programs. They're first-class values—you can pass them around, return them, and store them in data structures.
## Defining Functions
Basic syntax:
```lux
fn name(param1: Type1, param2: Type2): ReturnType = body
```
Examples:
```lux
fn add(a: Int, b: Int): Int = a + b
fn greet(name: String): String = "Hello, " + name
fn isEven(n: Int): Bool = n % 2 == 0
```
## Single Expression vs Block Body
Simple functions use `=`:
```lux
fn square(x: Int): Int = x * x
```
Complex functions use `= { ... }`:
```lux
fn classify(n: Int): String = {
let abs_n = if n < 0 then -n else n
if abs_n == 0 then "zero"
else if abs_n < 10 then "small"
else "large"
}
```
The last expression in a block is the return value. No `return` keyword needed.
## Type Inference
Return types can often be inferred:
```lux
fn add(a: Int, b: Int) = a + b // Returns Int
fn not(b: Bool) = !b // Returns Bool
```
But parameter types are always required:
```lux
fn double(x) = x * 2 // Error: parameter type required
```
## Anonymous Functions (Lambdas)
Functions without names:
```lux
fn(x: Int): Int => x * 2
```
Used with higher-order functions:
```lux
List.map([1, 2, 3], fn(x: Int): Int => x * 2) // [2, 4, 6]
List.filter([1, 2, 3, 4], fn(x: Int): Bool => x > 2) // [3, 4]
```
Store in variables:
```lux
let double = fn(x: Int): Int => x * 2
double(5) // 10
```
## Higher-Order Functions
Functions that take or return functions:
```lux
// Takes a function
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
apply(fn(x: Int): Int => x + 1, 5) // 6
// Returns a function
fn makeAdder(n: Int): fn(Int): Int =
fn(x: Int): Int => x + n
let add10 = makeAdder(10)
add10(5) // 15
add10(20) // 30
```
## Closures
Functions capture their environment:
```lux
fn counter(): fn(): Int with {State} = {
let count = 0
fn(): Int with {State} => {
State.put(State.get() + 1)
State.get()
}
}
// The returned function remembers `count`
```
More practical example:
```lux
fn makeMultiplier(factor: Int): fn(Int): Int =
fn(x: Int): Int => x * factor
let triple = makeMultiplier(3)
triple(4) // 12
triple(7) // 21
```
## Recursion
Functions can call themselves:
```lux
fn factorial(n: Int): Int =
if n <= 1 then 1
else n * factorial(n - 1)
factorial(5) // 120
```
## Tail Call Optimization
Lux optimizes tail-recursive functions:
```lux
// Not tail-recursive (stack grows)
fn factorial(n: Int): Int =
if n <= 1 then 1
else n * factorial(n - 1) // Must multiply AFTER recursive call
// Tail-recursive (constant stack)
fn factorialTail(n: Int, acc: Int): Int =
if n <= 1 then acc
else factorialTail(n - 1, n * acc) // Recursive call is LAST operation
fn factorial(n: Int): Int = factorialTail(n, 1)
```
The tail-recursive version won't overflow the stack.
## Function Composition
Combine functions:
```lux
fn compose<A, B, C>(f: fn(B): C, g: fn(A): B): fn(A): C =
fn(x: A): C => f(g(x))
fn addOne(x: Int): Int = x + 1
fn double(x: Int): Int = x * 2
let addOneThenDouble = compose(double, addOne)
addOneThenDouble(5) // 12 = (5 + 1) * 2
```
## Partial Application
Create new functions by fixing some arguments:
```lux
fn add(a: Int, b: Int): Int = a + b
// Manually partial apply
fn add5(b: Int): Int = add(5, b)
add5(3) // 8
```
## Pipeline Style
Chain operations readably:
```lux
// Without pipeline
toString(List.sum(List.map(List.filter([1,2,3,4,5], isEven), square)))
// With intermediate variables
let nums = [1, 2, 3, 4, 5]
let evens = List.filter(nums, isEven)
let squared = List.map(evens, square)
let total = List.sum(squared)
toString(total)
```
## Functions with Effects
Functions that perform effects declare them:
```lux
fn pureAdd(a: Int, b: Int): Int = a + b // No effects
fn printAdd(a: Int, b: Int): Unit with {Console} = {
let sum = a + b
Console.print("Sum: " + toString(sum))
}
```
Effects propagate:
```lux
fn helper(): Int with {Console} = {
Console.print("Computing...")
42
}
// Must also declare Console since it calls helper
fn main(): Int with {Console} = {
let x = helper()
x * 2
}
```
## Generic Functions
Functions that work with any type:
```lux
fn identity<T>(x: T): T = x
identity(42) // 42
identity("hello") // "hello"
identity(true) // true
fn pair<A, B>(a: A, b: B): (A, B) = (a, b)
pair(1, "one") // (1, "one")
```
## Summary
```lux
// Basic function
fn name(param: Type): Return = body
// Lambda
fn(x: Int): Int => x * 2
// Higher-order (takes function)
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
// Higher-order (returns function)
fn makeAdder(n: Int): fn(Int): Int = fn(x: Int): Int => x + n
// Generic
fn identity<T>(x: T): T = x
// With effects
fn greet(name: String): Unit with {Console} = Console.print("Hi " + name)
```
## Next
[Chapter 4: Data Types](04-data-types.md) - Algebraic data types and pattern matching.

331
docs/guide/04-data-types.md Normal file
View File

@@ -0,0 +1,331 @@
# Chapter 4: Data Types
Lux has algebraic data types (ADTs)—a powerful way to model data with variants and pattern matching.
## Defining Types
### Enums (Sum Types)
A type that can be one of several variants:
```lux
type Color =
| Red
| Green
| Blue
let c: Color = Red
```
### Variants with Data
Variants can carry data:
```lux
type Shape =
| Circle(Int) // radius
| Rectangle(Int, Int) // width, height
| Point
let s1 = Circle(5)
let s2 = Rectangle(10, 20)
let s3 = Point
```
### Named Fields
For clarity, use record variants:
```lux
type Person =
| Person { name: String, age: Int }
let alice = Person { name: "Alice", age: 30 }
```
## Pattern Matching
The `match` expression destructures data:
```lux
fn colorName(c: Color): String =
match c {
Red => "red",
Green => "green",
Blue => "blue"
}
```
### Extracting Data
```lux
fn area(s: Shape): Int =
match s {
Circle(r) => 3 * r * r, // Approximate π as 3
Rectangle(w, h) => w * h,
Point => 0
}
area(Circle(5)) // 75
area(Rectangle(4, 5)) // 20
```
### Exhaustiveness
The compiler ensures you handle all cases:
```lux
fn colorName(c: Color): String =
match c {
Red => "red",
Green => "green"
// Error: non-exhaustive pattern, missing Blue
}
```
### Wildcard Pattern
Use `_` to match anything:
```lux
fn isRed(c: Color): Bool =
match c {
Red => true,
_ => false // Matches Green, Blue, anything else
}
```
### Guards
Add conditions to patterns:
```lux
fn classify(n: Int): String =
match n {
0 => "zero",
n if n > 0 => "positive",
_ => "negative"
}
```
### Nested Patterns
Match deep structures:
```lux
type Expr =
| Num(Int)
| Add(Expr, Expr)
| Mul(Expr, Expr)
fn simplify(e: Expr): Expr =
match e {
Add(Num(0), x) => x, // 0 + x = x
Add(x, Num(0)) => x, // x + 0 = x
Mul(Num(0), _) => Num(0), // 0 * x = 0
Mul(_, Num(0)) => Num(0), // x * 0 = 0
Mul(Num(1), x) => x, // 1 * x = x
Mul(x, Num(1)) => x, // x * 1 = x
_ => e // No simplification
}
```
## Built-in ADTs
### Option<T>
For optional values:
```lux
type Option<T> =
| Some(T)
| None
```
Usage:
```lux
fn safeDivide(a: Int, b: Int): Option<Int> =
if b == 0 then None
else Some(a / b)
fn showResult(opt: Option<Int>): String =
match opt {
Some(n) => "Result: " + toString(n),
None => "Cannot divide by zero"
}
showResult(safeDivide(10, 2)) // "Result: 5"
showResult(safeDivide(10, 0)) // "Cannot divide by zero"
```
### Result<T, E>
For operations that can fail with an error:
```lux
type Result<T, E> =
| Ok(T)
| Err(E)
```
Usage:
```lux
fn parseAge(s: String): Result<Int, String> =
// Simplified - assume we have a real parser
if s == "42" then Ok(42)
else Err("Invalid age: " + s)
fn handleAge(r: Result<Int, String>): String =
match r {
Ok(age) => "Age is " + toString(age),
Err(msg) => "Error: " + msg
}
```
### List<T>
Lists are built-in but conceptually:
```lux
type List<T> =
| Nil
| Cons(T, List<T>)
```
Pattern match on lists:
```lux
fn sum(nums: List<Int>): Int =
match nums {
[] => 0,
[x, ...rest] => x + sum(rest)
}
fn length<T>(list: List<T>): Int =
match list {
[] => 0,
[_, ...rest] => 1 + length(rest)
}
```
## Recursive Types
Types can reference themselves:
```lux
type Tree<T> =
| Leaf(T)
| Node(Tree<T>, Tree<T>)
fn sumTree(t: Tree<Int>): Int =
match t {
Leaf(n) => n,
Node(left, right) => sumTree(left) + sumTree(right)
}
let tree = Node(Node(Leaf(1), Leaf(2)), Leaf(3))
sumTree(tree) // 6
```
## Type Aliases
Give names to existing types:
```lux
type UserId = Int
type Username = String
type UserMap = List<(UserId, Username)>
```
## Records
Anonymous record types:
```lux
let point = { x: 10, y: 20 }
point.x // 10
point.y // 20
// With type annotation
type Point = { x: Int, y: Int }
let p: Point = { x: 5, y: 10 }
```
### Record Update
Create new records based on existing ones:
```lux
let p1 = { x: 10, y: 20 }
let p2 = { ...p1, x: 15 } // { x: 15, y: 20 }
```
## Practical Example: Expression Evaluator
```lux
type Expr =
| Num(Int)
| Add(Expr, Expr)
| Sub(Expr, Expr)
| Mul(Expr, Expr)
| Div(Expr, Expr)
fn eval(e: Expr): Result<Int, String> =
match e {
Num(n) => Ok(n),
Add(a, b) => {
match (eval(a), eval(b)) {
(Ok(x), Ok(y)) => Ok(x + y),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
},
Sub(a, b) => {
match (eval(a), eval(b)) {
(Ok(x), Ok(y)) => Ok(x - y),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
},
Mul(a, b) => {
match (eval(a), eval(b)) {
(Ok(x), Ok(y)) => Ok(x * y),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
},
Div(a, b) => {
match (eval(a), eval(b)) {
(Ok(_), Ok(0)) => Err("Division by zero"),
(Ok(x), Ok(y)) => Ok(x / y),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
}
}
// (10 + 5) * 2
let expr = Mul(Add(Num(10), Num(5)), Num(2))
eval(expr) // Ok(30)
// 10 / 0
let bad = Div(Num(10), Num(0))
eval(bad) // Err("Division by zero")
```
## Summary
| Concept | Syntax | Example |
|---------|--------|---------|
| Enum | `type T = \| A \| B` | `type Bool = \| True \| False` |
| With data | `\| Variant(Type)` | `\| Some(Int)` |
| Match | `match x { ... }` | `match opt { Some(n) => n, None => 0 }` |
| Wildcard | `_` | `_ => "default"` |
| Guard | `pattern if cond` | `n if n > 0 => "positive"` |
| Record | `{ field: value }` | `{ x: 10, y: 20 }` |
## Next
[Chapter 5: Effects](05-effects.md) - The core innovation of Lux.

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.

348
docs/guide/06-handlers.md Normal file
View File

@@ -0,0 +1,348 @@
# Chapter 6: Handlers
Handlers are where effects become powerful. They define *how* effect operations behave, and they can do surprising things.
## Basic Handler Structure
```lux
handler handlerName: EffectName {
fn operation1(param1, param2) = {
// Implementation
resume(result)
}
fn operation2(param) = {
// Implementation
resume(result)
}
}
```
Key points:
- Handlers implement all operations of an effect
- `resume(value)` continues the computation with `value`
- Handlers can use other effects in their implementations
## Understanding Resume
`resume` is the key to handler power. It continues the computation from where the effect was called.
```lux
effect Ask {
fn ask(prompt: String): Int
}
fn computation(): Int with {Ask} = {
let x = Ask.ask("first") // Pauses here
let y = Ask.ask("second") // Then here
x + y
}
handler doubler: Ask {
fn ask(prompt) = resume(2) // Always returns 2
}
run computation() with { Ask = doubler }
// Returns: 4 (2 + 2)
```
The flow:
1. `computation` calls `Ask.ask("first")`
2. Handler receives control, calls `resume(2)`
3. `computation` continues with `x = 2`
4. `computation` calls `Ask.ask("second")`
5. Handler receives control, calls `resume(2)`
6. `computation` continues with `y = 2`
7. Returns `4`
## Handlers That Don't Resume
Not all handlers must resume. Some can abort:
```lux
effect Validate {
fn check(condition: Bool, message: String): Unit
}
fn validateAge(age: Int): String with {Validate} = {
Validate.check(age >= 0, "Age cannot be negative")
Validate.check(age < 150, "Age seems unrealistic")
"Valid age: " + toString(age)
}
handler strictValidator: Validate {
fn check(cond, msg) =
if cond then resume(())
else "Validation failed: " + msg // Returns early, doesn't resume
}
run validateAge(-5) with { Validate = strictValidator }
// Returns: "Validation failed: Age cannot be negative"
```
## Handlers Using Other Effects
Handlers can use effects in their implementation:
```lux
effect Logger {
fn log(msg: String): Unit
}
handler consoleLogger: Logger {
fn log(msg) = {
Console.print("[LOG] " + msg) // Uses Console effect
resume(())
}
}
fn main(): Unit with {Console} = {
let result = run {
Logger.log("Hello")
Logger.log("World")
42
} with { Logger = consoleLogger }
Console.print("Result: " + toString(result))
}
```
## Stateful Handlers
Handlers can maintain state using the State effect:
```lux
effect Counter {
fn increment(): Unit
fn get(): Int
}
handler counterHandler: Counter {
fn increment() = {
State.put(State.get() + 1)
resume(())
}
fn get() = resume(State.get())
}
fn counting(): Int with {Counter} = {
Counter.increment()
Counter.increment()
Counter.increment()
Counter.get()
}
fn main(): Unit with {Console} = {
let result = run {
run counting() with { Counter = counterHandler }
} with { State = 0 }
Console.print("Count: " + toString(result)) // Count: 3
}
```
## Handler Patterns
### The Reader Pattern
Provide read-only context:
```lux
effect Config {
fn get(key: String): String
}
handler envConfig: Config {
fn get(key) = resume(Process.env(key))
}
handler mapConfig(settings: Map<String, String>): Config {
fn get(key) = resume(Map.getOrDefault(settings, key, ""))
}
```
### The Writer Pattern
Accumulate output:
```lux
effect Log {
fn write(msg: String): Unit
}
handler collectLogs: Log {
fn write(msg) = {
State.put(State.get() + msg + "\n")
resume(())
}
}
fn program(): Int with {Log} = {
Log.write("Starting")
let result = 42
Log.write("Done")
result
}
// Get both result and logs
let (result, logs) = run {
run program() with { Log = collectLogs }
let logs = State.get()
(result, logs)
} with { State = "" }
```
### The Exception Pattern
Early termination with cleanup:
```lux
effect Fail {
fn fail(msg: String): Unit
}
handler catchFail: Fail {
fn fail(msg) = Err(msg) // Don't resume, return error
}
fn riskyOperation(): Int with {Fail} = {
if Random.bool() then Fail.fail("Bad luck!")
else 42
}
let result: Result<Int, String> = run riskyOperation() with { Fail = catchFail }
```
### The Choice Pattern
Non-determinism:
```lux
effect Choice {
fn choose(options: List<T>): T
}
fn picker(): Int with {Choice} = {
let x = Choice.choose([1, 2, 3])
let y = Choice.choose([10, 20])
x + y
}
// Handler that returns first option
handler firstChoice: Choice {
fn choose(opts) = resume(List.head(opts))
}
// Handler that returns all combinations
handler allChoices: Choice {
fn choose(opts) =
List.flatMap(opts, fn(opt: T): List<T> => resume(opt))
}
```
## Combining Multiple Handlers
Multiple effects, multiple handlers:
```lux
effect Logger { fn log(msg: String): Unit }
effect Counter { fn count(): Int }
fn program(): Int with {Logger, Counter} = {
Logger.log("Starting")
let n = Counter.count()
Logger.log("Got " + toString(n))
n * 2
}
handler myLogger: Logger {
fn log(msg) = { Console.print(msg); resume(()) }
}
handler myCounter: Counter {
fn count() = resume(42)
}
let result = run program() with {
Logger = myLogger,
Counter = myCounter
}
```
## Handler Scope
Handlers apply to their `run` block:
```lux
handler loudLogger: Logger {
fn log(msg) = { Console.print("!!! " + msg + " !!!"); resume(()) }
}
handler quietLogger: Logger {
fn log(msg) = resume(()) // Silent
}
fn program(): Unit with {Logger, Console} = {
Logger.log("Outer")
let inner = run {
Logger.log("Inner")
42
} with { Logger = quietLogger }
Logger.log("Back to outer")
Console.print("Inner result: " + toString(inner))
}
run program() with { Logger = loudLogger }
// Output:
// !!! Outer !!!
// !!! Back to outer !!!
// Inner result: 42
```
The inner `run` uses `quietLogger`, so "Inner" is silent.
## Real-World Example: Database Testing
```lux
effect Database {
fn query(sql: String): List<Row>
fn execute(sql: String): Int
}
// Production handler using real database
handler postgresDb(conn: Connection): Database {
fn query(sql) = resume(Postgres.query(conn, sql))
fn execute(sql) = resume(Postgres.execute(conn, sql))
}
// Test handler using in-memory data
handler mockDb(data: List<Row>): Database {
fn query(sql) = resume(data)
fn execute(sql) = resume(1)
}
fn getUserCount(): Int with {Database} = {
let rows = Database.query("SELECT COUNT(*) FROM users")
extractCount(rows)
}
// Production
run getUserCount() with { Database = postgresDb(realConnection) }
// Testing - no database needed!
run getUserCount() with { Database = mockDb([Row { count: 42 }]) }
```
## Summary
| Pattern | Use Case | Resume? |
|---------|----------|---------|
| Basic | Provide implementation | Yes |
| Early return | Validation, errors | No |
| Reader | Configuration | Yes |
| Writer | Logging, accumulation | Yes |
| State | Counters, caches | Yes |
| Exception | Error handling | Sometimes |
## Next
[Chapter 7: Modules](07-modules.md) - Organizing code across files.

320
docs/guide/07-modules.md Normal file
View File

@@ -0,0 +1,320 @@
# 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.

294
docs/guide/08-errors.md Normal file
View File

@@ -0,0 +1,294 @@
# Chapter 8: Error Handling
Errors happen. Lux provides multiple ways to handle them, all explicit in the type system.
## The Fail Effect
The simplest error handling uses the built-in `Fail` effect:
```lux
fn divide(a: Int, b: Int): Int with {Fail} =
if b == 0 then Fail.fail("Division by zero")
else a / b
fn main(): Unit with {Console} = {
let result = run divide(10, 2) with {}
Console.print("Result: " + toString(result))
}
```
When `b == 0`, `Fail.fail` stops execution. The program terminates with an error.
### Fail Propagates
```lux
fn helper(): Int with {Fail} =
Fail.fail("Oops")
fn caller(): Int with {Fail} = {
let x = helper() // Fails here
x * 2 // Never reached
}
```
If you call a function with `Fail`, you must declare it:
```lux
// Error: caller uses Fail but doesn't declare it
fn broken(): Int = {
divide(10, 0)
}
```
## Option<T> - Maybe There's a Value
For operations that might not have a result:
```lux
fn safeDivide(a: Int, b: Int): Option<Int> =
if b == 0 then None
else Some(a / b)
fn findUser(id: Int): Option<User> =
// Returns None if user doesn't exist
Database.query("SELECT * FROM users WHERE id = " + toString(id))
```
### Working with Option
**Pattern matching:**
```lux
fn showResult(opt: Option<Int>): String =
match opt {
Some(n) => "Got: " + toString(n),
None => "No value"
}
```
**Option methods:**
```lux
let x = Some(5)
let y: Option<Int> = None
Option.map(x, fn(n: Int): Int => n * 2) // Some(10)
Option.map(y, fn(n: Int): Int => n * 2) // None
Option.getOrElse(x, 0) // 5
Option.getOrElse(y, 0) // 0
Option.isSome(x) // true
Option.isNone(y) // true
```
**Chaining with flatMap:**
```lux
fn getUserName(id: Int): Option<String> =
Option.flatMap(findUser(id), fn(user: User): Option<String> =>
Some(user.name)
)
```
## Result<T, E> - Success or Failure
For operations that can fail with an error value:
```lux
fn parseNumber(s: String): Result<Int, String> =
if isNumeric(s) then Ok(parseInt(s))
else Err("Not a number: " + s)
fn readConfig(path: String): Result<Config, String> with {File} =
if File.exists(path) then
match parseConfig(File.read(path)) {
Some(c) => Ok(c),
None => Err("Invalid config format")
}
else Err("Config file not found: " + path)
```
### Working with Result
**Pattern matching:**
```lux
fn handleResult(r: Result<Int, String>): String =
match r {
Ok(n) => "Success: " + toString(n),
Err(e) => "Error: " + e
}
```
**Result methods:**
```lux
let success = Ok(42)
let failure: Result<Int, String> = Err("oops")
Result.map(success, fn(n: Int): Int => n * 2) // Ok(84)
Result.map(failure, fn(n: Int): Int => n * 2) // Err("oops")
Result.getOrElse(success, 0) // 42
Result.getOrElse(failure, 0) // 0
Result.isOk(success) // true
Result.isErr(failure) // true
```
**Chaining operations:**
```lux
fn processData(input: String): Result<Output, String> = {
let parsed = parseInput(input) // Result<Input, String>
let validated = Result.flatMap(parsed, validate) // Result<Input, String>
Result.map(validated, transform) // Result<Output, String>
}
```
## Combining Approaches
### Option to Result
```lux
fn optionToResult<T>(opt: Option<T>, error: String): Result<T, String> =
match opt {
Some(v) => Ok(v),
None => Err(error)
}
fn findUserOrError(id: Int): Result<User, String> =
optionToResult(findUser(id), "User not found: " + toString(id))
```
### Result to Option
```lux
fn resultToOption<T, E>(r: Result<T, E>): Option<T> =
match r {
Ok(v) => Some(v),
Err(_) => None
}
```
### Fail with Result
```lux
fn processOrFail(input: String): Output with {Fail} =
match process(input) {
Ok(output) => output,
Err(e) => Fail.fail(e)
}
```
## Error Handling Patterns
### Early Return with Fail
```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 if user.email == "" then Fail.fail("Email required")
else user
}
fn registerUser(user: User): UserId with {Fail, Database} = {
let validated = validateUser(user)
Database.insert(validated)
}
```
### Collecting Errors
```lux
fn validateAll(user: User): Result<User, List<String>> = {
let errors: List<String> = []
let errors = if user.name == "" then List.concat(errors, ["Name required"]) else errors
let errors = if user.age < 0 then List.concat(errors, ["Invalid age"]) else errors
let errors = if user.email == "" then List.concat(errors, ["Email required"]) else errors
if List.isEmpty(errors) then Ok(user)
else Err(errors)
}
```
### Default Values
```lux
fn getConfig(key: String): String =
Option.getOrElse(Config.get(key), "default")
fn getPort(): Int =
Result.getOrElse(parseNumber(Process.env("PORT")), 8080)
```
### Logging Errors
```lux
fn processWithLogging(input: String): Result<Output, String> with {Console} = {
let result = process(input)
match result {
Ok(_) => result,
Err(e) => {
Console.print("Error processing input: " + e)
result
}
}
}
```
## Custom Error Types
Define specific error types:
```lux
type ValidationError =
| MissingField(String)
| InvalidFormat(String, String) // field, expected format
| OutOfRange(String, Int, Int) // field, min, max
fn validate(user: User): Result<User, ValidationError> = {
if user.name == "" then Err(MissingField("name"))
else if user.age < 0 then Err(OutOfRange("age", 0, 150))
else if !isValidEmail(user.email) then Err(InvalidFormat("email", "user@domain.com"))
else Ok(user)
}
fn showError(e: ValidationError): String =
match e {
MissingField(f) => "Missing required field: " + f,
InvalidFormat(f, fmt) => f + " must be in format: " + fmt,
OutOfRange(f, min, max) =>
f + " must be between " + toString(min) + " and " + toString(max)
}
```
## When to Use What
| Scenario | Use |
|----------|-----|
| Might not exist | `Option<T>` |
| Can fail with reason | `Result<T, E>` |
| Fatal error, stop execution | `Fail` effect |
| Multiple error types | Custom error ADT |
## Summary
```lux
// Option - maybe a value
let opt: Option<Int> = Some(42)
let none: Option<Int> = None
// Result - success or error
let ok: Result<Int, String> = Ok(42)
let err: Result<Int, String> = Err("failed")
// Fail effect - abort execution
fn risky(): Int with {Fail} = Fail.fail("boom")
// Pattern match to handle
match result {
Ok(v) => handleSuccess(v),
Err(e) => handleError(e)
}
```
## Next
[Chapter 9: Standard Library](09-stdlib.md) - Built-in functions and modules.

318
docs/guide/09-stdlib.md Normal file
View File

@@ -0,0 +1,318 @@
# Chapter 9: Standard Library
Lux comes with a comprehensive standard library. This chapter covers the built-in modules.
## Built-in Functions
Always available, no import needed:
```lux
toString(42) // "42" - convert any value to string
typeOf(42) // "Int" - get type name
print("hello") // Print to console (shortcut)
```
## List Module
Operations on lists:
```lux
let nums = [1, 2, 3, 4, 5]
// Transformations
List.map(nums, fn(x: Int): Int => x * 2) // [2, 4, 6, 8, 10]
List.filter(nums, fn(x: Int): Bool => x > 2) // [3, 4, 5]
List.fold(nums, 0, fn(acc: Int, x: Int): Int => acc + x) // 15
// Access
List.head(nums) // Some(1)
List.tail(nums) // [2, 3, 4, 5]
List.get(nums, 2) // Some(3)
List.length(nums) // 5
List.isEmpty([]) // true
// Building
List.range(1, 5) // [1, 2, 3, 4]
List.concat([1,2], [3,4]) // [1, 2, 3, 4]
List.reverse(nums) // [5, 4, 3, 2, 1]
// Searching
List.find(nums, fn(x: Int): Bool => x > 3) // Some(4)
List.any(nums, fn(x: Int): Bool => x > 3) // true
List.all(nums, fn(x: Int): Bool => x > 0) // true
// Slicing
List.take(nums, 3) // [1, 2, 3]
List.drop(nums, 3) // [4, 5]
```
## String Module
String manipulation:
```lux
let s = "Hello, World!"
// Info
String.length(s) // 13
String.isEmpty("") // true
// Search
String.contains(s, "World") // true
String.startsWith(s, "Hello") // true
String.endsWith(s, "!") // true
// Transform
String.toUpper(s) // "HELLO, WORLD!"
String.toLower(s) // "hello, world!"
String.trim(" hi ") // "hi"
String.replace(s, "World", "Lux") // "Hello, Lux!"
// Split/Join
String.split("a,b,c", ",") // ["a", "b", "c"]
String.join(["a","b","c"], "-") // "a-b-c"
String.lines("a\nb\nc") // ["a", "b", "c"]
String.chars("abc") // ["a", "b", "c"]
// Substring
String.substring(s, 0, 5) // "Hello"
```
## Option Module
Working with optional values:
```lux
let some = Some(42)
let none: Option<Int> = None
// Check
Option.isSome(some) // true
Option.isNone(none) // true
// Transform
Option.map(some, fn(x: Int): Int => x * 2) // Some(84)
Option.flatMap(some, fn(x: Int): Option<Int> => Some(x + 1)) // Some(43)
// Extract
Option.getOrElse(some, 0) // 42
Option.getOrElse(none, 0) // 0
```
## Result Module
Working with results:
```lux
let ok: Result<Int, String> = Ok(42)
let err: Result<Int, String> = Err("oops")
// Check
Result.isOk(ok) // true
Result.isErr(err) // true
// Transform
Result.map(ok, fn(x: Int): Int => x * 2) // Ok(84)
Result.mapErr(err, fn(e: String): String => "Error: " + e) // Err("Error: oops")
Result.flatMap(ok, fn(x: Int): Result<Int, String> => Ok(x + 1)) // Ok(43)
// Extract
Result.getOrElse(ok, 0) // 42
Result.getOrElse(err, 0) // 0
```
## Math Module
Mathematical functions:
```lux
Math.abs(-5) // 5
Math.min(3, 7) // 3
Math.max(3, 7) // 7
Math.pow(2, 10) // 1024
Math.sqrt(16) // 4.0
Math.floor(3.7) // 3
Math.ceil(3.2) // 4
Math.round(3.5) // 4
```
## Json Module
JSON parsing and generation:
```lux
// Parse JSON string
let data = Json.parse("{\"name\": \"Alice\", \"age\": 30}")
// Access fields
Json.get(data, "name") // Some("Alice")
Json.getInt(data, "age") // Some(30)
// Create JSON
let obj = Json.object([
("name", Json.string("Bob")),
("age", Json.int(25))
])
// Convert to string
Json.stringify(obj) // "{\"name\":\"Bob\",\"age\":25}"
Json.prettyPrint(obj) // Formatted with indentation
```
## Standard Library Modules (std/)
Import from the `std/` directory:
### std/prelude
```lux
import std/prelude.*
identity(42) // 42
compose(f, g) // Function composition
flip(f) // Flip argument order
not(true) // false
and(true, false) // false
or(true, false) // true
```
### std/io
```lux
import std/io
io.println("Hello") // Print with newline
io.print("No newline") // Print without newline
io.readLine() // Read line from input
io.debug("label", value) // Debug print, returns value
```
### std/option
```lux
import std/option as opt
opt.some(42) // Some(42)
opt.none() // None
opt.map(x, f) // Map function over option
opt.flatMap(x, f) // FlatMap
opt.filter(x, pred) // Filter by predicate
opt.toList(x) // Convert to list
```
### std/result
```lux
import std/result as res
res.ok(42) // Ok(42)
res.err("oops") // Err("oops")
res.mapOk(r, f) // Map over Ok value
res.mapErr(r, f) // Map over Err value
res.unwrapOr(r, default) // Get value or default
```
## Built-in Effects
### Console
```lux
fn example(): Unit with {Console} = {
Console.print("Hello") // Print string
let line = Console.readLine() // Read line
let num = Console.readInt() // Read integer
}
```
### File
```lux
fn example(): Unit with {File} = {
let content = File.read("file.txt") // Read file
File.write("out.txt", "content") // Write file
let exists = File.exists("file.txt") // Check existence
let files = File.list("./dir") // List directory
File.mkdir("newdir") // Create directory
File.delete("file.txt") // Delete file
}
```
### Process
```lux
fn example(): Unit with {Process} = {
let output = Process.exec("ls", ["-la"]) // Run command
let home = Process.env("HOME") // Get env var
let args = Process.args() // Get CLI args
let cwd = Process.cwd() // Current directory
Process.exit(0) // Exit program
}
```
### Http
```lux
fn example(): Unit with {Http} = {
let body = Http.get("https://api.example.com/data")
let response = Http.post("https://api.example.com/data", jsonBody)
Http.put("https://api.example.com/data/1", updatedBody)
Http.delete("https://api.example.com/data/1")
}
```
### Random
```lux
fn example(): Unit with {Random} = {
let n = Random.int(1, 100) // Random int in range
let f = Random.float() // Random float 0.0-1.0
let b = Random.bool() // Random boolean
}
```
### State
```lux
fn example(): Int with {State} = {
let current = State.get() // Get current state
State.put(current + 1) // Update state
State.get()
}
// Initialize with value
run example() with { State = 0 }
```
### Fail
```lux
fn example(): Int with {Fail} = {
if condition then Fail.fail("Error message")
else 42
}
```
## Quick Reference
| Module | Key Functions |
|--------|---------------|
| List | map, filter, fold, head, tail, length, concat |
| String | split, join, trim, contains, replace, toUpper |
| Option | map, flatMap, getOrElse, isSome, isNone |
| Result | map, mapErr, flatMap, getOrElse, isOk |
| Math | abs, min, max, pow, sqrt, floor, ceil |
| Json | parse, stringify, get, object, array |
| Effect | Operations |
|--------|------------|
| Console | print, readLine, readInt |
| File | read, write, exists, list, mkdir, delete |
| Process | exec, env, args, cwd, exit |
| Http | get, post, put, delete |
| Random | int, float, bool |
| State | get, put |
| Fail | fail |
## Next
[Chapter 10: Advanced Topics](10-advanced.md) - Traits, generics, and optimization.

325
docs/guide/10-advanced.md Normal file
View File

@@ -0,0 +1,325 @@
# Chapter 10: Advanced Topics
This chapter covers advanced features for building larger applications.
## Traits
Traits define shared behavior across types:
```lux
trait Show {
fn show(self): String
}
impl Show for Int {
fn show(self): String = toString(self)
}
impl Show for Bool {
fn show(self): String = if self then "true" else "false"
}
impl Show for List<T> where T: Show {
fn show(self): String = {
let items = List.map(self, fn(x: T): String => x.show())
"[" + String.join(items, ", ") + "]"
}
}
```
Using traits:
```lux
fn display<T>(value: T): Unit with {Console} where T: Show =
Console.print(value.show())
display(42) // "42"
display(true) // "true"
display([1, 2, 3]) // "[1, 2, 3]"
```
## Generic Types
Types with parameters:
```lux
type Pair<A, B> =
| MkPair(A, B)
fn first<A, B>(p: Pair<A, B>): A =
match p {
MkPair(a, _) => a
}
fn second<A, B>(p: Pair<A, B>): B =
match p {
MkPair(_, b) => b
}
let p = MkPair(1, "one")
first(p) // 1
second(p) // "one"
```
## Type Constraints
Restrict generic types:
```lux
fn maximum<T>(list: List<T>): Option<T> where T: Ord = {
match list {
[] => None,
[x] => Some(x),
[x, ...rest] => {
match maximum(rest) {
None => Some(x),
Some(y) => Some(if x > y then x else y)
}
}
}
}
```
## Tail Call Optimization
Lux optimizes tail-recursive functions:
```lux
// Not tail-recursive - stack grows with each call
fn sumBad(n: Int): Int =
if n <= 0 then 0
else n + sumBad(n - 1) // Addition happens AFTER recursive call
// Tail-recursive - constant stack space
fn sumGood(n: Int, acc: Int): Int =
if n <= 0 then acc
else sumGood(n - 1, acc + n) // Recursive call is the LAST operation
fn sum(n: Int): Int = sumGood(n, 0)
```
The compiler transforms tail calls into loops, preventing stack overflow.
## Effect Polymorphism
Functions can be polymorphic over effects:
```lux
fn withLogging<E>(action: fn(): Int with {E}): Int with {E, Console} = {
Console.print("Starting action")
let result = action()
Console.print("Action returned: " + toString(result))
result
}
// Works with any effect set
fn pureAction(): Int = 42
fn randomAction(): Int with {Random} = Random.int(1, 100)
withLogging(pureAction) // Works
withLogging(randomAction) // Works
```
## Behavioral Properties
Annotate functions with properties:
```lux
// Pure function - no effects
fn add(a: Int, b: Int): Int is pure = a + b
// Total function - always terminates
fn factorial(n: Int): Int is total =
if n <= 1 then 1 else n * factorial(n - 1)
// Idempotent - same result if called multiple times
fn setConfig(key: String, value: String): Unit with {State} is idempotent =
State.put(value)
```
These are currently documentation, but future versions may verify them.
## Documentation Comments
Use `///` for documentation:
```lux
/// Calculates the factorial of a non-negative integer.
///
/// # Arguments
/// * `n` - A non-negative integer
///
/// # Returns
/// The factorial of n (n!)
///
/// # Example
/// ```
/// factorial(5) // Returns 120
/// ```
pub fn factorial(n: Int): Int =
if n <= 1 then 1 else n * factorial(n - 1)
```
## Performance Tips
### 1. Use Tail Recursion
```lux
// Slow - builds up stack
fn lengthSlow<T>(list: List<T>): Int =
match list {
[] => 0,
[_, ...rest] => 1 + lengthSlow(rest)
}
// Fast - constant stack
fn lengthFast<T>(list: List<T>, acc: Int): Int =
match list {
[] => acc,
[_, ...rest] => lengthFast(rest, acc + 1)
}
```
### 2. Avoid Repeated Concatenation
```lux
// Slow - O(n²)
fn buildStringSlow(n: Int): String =
if n <= 0 then ""
else buildStringSlow(n - 1) + "x"
// Fast - use List.join
fn buildStringFast(n: Int): String =
String.join(List.map(List.range(0, n), fn(_: Int): String => "x"), "")
```
### 3. Use Built-in Functions
```lux
// Slow - manual implementation
fn sumManual(nums: List<Int>): Int =
match nums {
[] => 0,
[x, ...rest] => x + sumManual(rest)
}
// Fast - built-in fold
fn sumBuiltin(nums: List<Int>): Int =
List.fold(nums, 0, fn(acc: Int, x: Int): Int => acc + x)
```
### 4. JIT Compilation
For performance-critical numeric code, the JIT compiler provides ~160x speedup:
```lux
// In Rust code, use the JIT compiler
let mut jit = JitCompiler::new().unwrap();
jit.compile_function(&func).unwrap();
let result = unsafe { jit.call_function("fib", &[30]).unwrap() };
```
## Debugging
### Debug Printing
```lux
fn debug<T>(label: String, value: T): T with {Console} = {
Console.print(label + ": " + toString(value))
value
}
fn process(x: Int): Int with {Console} = {
let step1 = debug("step1", x * 2)
let step2 = debug("step2", step1 + 10)
step2
}
```
### The Debugger
Run with debugger:
```bash
lux --debug program.lux
```
Commands:
- `step` / `s` - Step into
- `next` / `n` - Step over
- `continue` / `c` - Continue
- `print <expr>` - Evaluate expression
- `break <line>` - Set breakpoint
- `quit` / `q` - Exit
### Effect Tracing
```lux
fn traced<E>(action: fn(): T with {E}): T with {E, Console} = {
Console.print(">>> Entering action")
let result = action()
Console.print("<<< Exiting with: " + toString(result))
result
}
```
## IDE Support
Lux has LSP support for:
- **VS Code**: Install Lux extension
- **Neovim**: Configure with nvim-lspconfig
Features:
- Syntax highlighting
- Error diagnostics
- Go to definition
- Hover for types
- Auto-completion
Start LSP server:
```bash
lux --lsp
```
## Project Structure
Recommended layout for larger projects:
```
my-project/
├── lux.toml # Project manifest
├── src/
│ ├── main.lux # Entry point
│ ├── lib.lux # Library code
│ └── modules/
│ ├── users.lux
│ └── orders.lux
├── std/ # Custom std extensions
├── tests/
│ ├── users_test.lux
│ └── orders_test.lux
└── examples/
└── demo.lux
```
## Summary
| Feature | Syntax |
|---------|--------|
| Trait | `trait Name { fn method(self): T }` |
| Impl | `impl Trait for Type { ... }` |
| Generic | `fn f<T>(x: T): T` |
| Constraint | `where T: Trait` |
| Tail recursion | Last expression is recursive call |
| Doc comment | `/// Documentation` |
## What's Next?
You now know Lux! Try:
1. **Build something**: See [Tutorials](../tutorials/README.md)
2. **Read the reference**: See [Language Reference](../reference/syntax.md)
3. **Explore effects**: See [Effects Cookbook](../tutorials/effects-cookbook.md)
4. **Join the community**: GitHub discussions
Happy coding with Lux!