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

227
docs/README.md Normal file
View File

@@ -0,0 +1,227 @@
# Lux Documentation
**Lux** is a functional programming language with first-class algebraic effects. It combines the safety of static typing with the flexibility of effect handlers, letting you write pure code that can do impure things—without losing control.
## Quick Links
| I want to... | Go to... |
|--------------|----------|
| Try Lux in 5 minutes | [Quick Start](#quick-start) |
| Learn Lux systematically | [The Lux Guide](guide/01-introduction.md) |
| Understand effects | [Effects Guide](guide/05-effects.md) |
| Look up syntax | [Language Reference](reference/syntax.md) |
| See the standard library | [Standard Library](reference/stdlib.md) |
| Build something | [Tutorials](tutorials/README.md) |
---
## Quick Start
### Install
```bash
# Clone and build
git clone https://github.com/your-org/lux
cd lux
cargo build --release
# Or with Nix
nix build
```
### Hello World
Create `hello.lux`:
```lux
fn main(): Unit with {Console} =
Console.print("Hello, World!")
let output = run main() with {}
```
Run it:
```bash
lux hello.lux
# Output: Hello, World!
```
### The REPL
```bash
$ lux
Lux v0.1.0 - Type :help for commands
> 1 + 2 * 3
7
> fn square(x: Int): Int = x * x
<function>
> square(5)
25
> List.map([1, 2, 3], fn(x: Int): Int => x * 2)
[2, 4, 6]
```
---
## What Makes Lux Different?
### 1. Effects Are Explicit
In most languages, a function can do anything—print, crash, launch missiles—and the type doesn't tell you. In Lux, side effects are tracked:
```lux
// This function ONLY does math—the type guarantees it
fn add(a: Int, b: Int): Int = a + b
// This function uses Console—you can see it in the signature
fn greet(name: String): Unit with {Console} =
Console.print("Hello, " + name)
```
### 2. Effects Are Handleable
Effects aren't just tracked—they're *values* you can intercept and redefine:
```lux
effect Logger {
fn log(msg: String): Unit
}
fn compute(): Int with {Logger} = {
Logger.log("Starting")
let result = 42
Logger.log("Done")
result
}
// Run with console logging
handler consoleLogger: Logger {
fn log(msg) = {
Console.print("[LOG] " + msg)
resume(())
}
}
let result = run compute() with { Logger = consoleLogger }
```
### 3. Handlers Can Resume
Unlike exceptions, effect handlers can *continue* the computation:
```lux
effect Ask {
fn ask(question: String): String
}
fn survey(): Unit with {Ask, Console} = {
let name = Ask.ask("What's your name?")
let color = Ask.ask("Favorite color?")
Console.print(name + " likes " + color)
}
// Handler that provides answers
handler autoAnswer: Ask {
fn ask(q) =
if String.contains(q, "name") then resume("Alice")
else resume("blue")
}
run survey() with { Ask = autoAnswer }
// Output: Alice likes blue
```
---
## Documentation Structure
### [The Lux Guide](guide/01-introduction.md)
A sequential guide to learning Lux, from basics to advanced topics.
1. [Introduction](guide/01-introduction.md) - Why Lux, installation, first program
2. [Basic Types](guide/02-basic-types.md) - Int, String, Bool, functions
3. [Functions](guide/03-functions.md) - Definitions, closures, higher-order
4. [Data Types](guide/04-data-types.md) - ADTs, pattern matching, records
5. [Effects](guide/05-effects.md) - The core innovation
6. [Handlers](guide/06-handlers.md) - Defining and using handlers
7. [Modules](guide/07-modules.md) - Code organization, imports
8. [Error Handling](guide/08-errors.md) - Fail effect, Option, Result
9. [Standard Library](guide/09-stdlib.md) - Built-in functions
10. [Advanced Topics](guide/10-advanced.md) - Traits, generics, optimization
### [Language Reference](reference/syntax.md)
Complete syntax and semantics reference.
- [Syntax](reference/syntax.md) - Grammar and syntax rules
- [Types](reference/types.md) - Type system details
- [Effects](reference/effects.md) - Effect system reference
- [Standard Library](reference/stdlib.md) - All built-in functions
### [Tutorials](tutorials/README.md)
Project-based learning.
**Standard Programs:**
- [Calculator](tutorials/calculator.md) - Basic REPL calculator
- [Todo App](tutorials/todo.md) - File I/O and data structures
- [HTTP Client](tutorials/http-client.md) - Fetching web data
**Effect Showcases:**
- [Dependency Injection](tutorials/dependency-injection.md) - Testing with effects
- [State Machines](tutorials/state-machines.md) - Modeling state with effects
- [Parser Combinators](tutorials/parsers.md) - Effects for backtracking
---
## Project Ideas
Once you've learned the basics, try building:
### Beginner
- **Unit converter** - Temperatures, distances, weights
- **Word counter** - Count words, lines, characters in files
- **Quiz game** - Random questions with scoring
### Intermediate
- **Markdown parser** - Parse and render Markdown
- **Task manager** - CLI todo list with file persistence
- **API client** - Fetch and display data from a REST API
### Advanced
- **Effect-based testing framework** - Use effects for test isolation
- **Configuration DSL** - Effect-based config with validation
- **Interpreter** - Build a small language interpreter
### Showcasing Effects
- **Transactional file system** - Rollback on failure using effects
- **Mock HTTP for testing** - Swap real HTTP with mock handlers
- **Async simulation** - Model concurrency with effect handlers
- **Capability-based security** - Effects as capabilities
---
## Philosophy
Lux is designed around these principles:
1. **Explicitness over magic** - Effects in the type signature, not hidden
2. **Composition over inheritance** - Effects combine freely
3. **Safety with escape hatches** - Type safety by default, but practical
4. **Functional core, imperative shell** - Pure logic, effectful boundaries
---
## Getting Help
- **REPL**: Type `:help` for commands
- **LSP**: Full IDE support in VS Code, Neovim
- **Examples**: See `examples/` directory
- **Issues**: Report bugs on GitHub
---
*Lux: Where effects are first-class citizens.*

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!

509
docs/reference/syntax.md Normal file
View File

@@ -0,0 +1,509 @@
# Language Reference: Syntax
Complete syntax reference for Lux.
## Lexical Structure
### Comments
```lux
// Single-line comment
/* Multi-line
comment */
/// Documentation comment (for declarations)
```
### Identifiers
```
identifier = letter (letter | digit | '_')*
type_name = upper_letter (letter | digit)*
```
Examples: `foo`, `myVar`, `Type`, `Option`
### Literals
```lux
// Integers
42
-17
1_000_000 // Underscores for readability
0xFF // Hexadecimal
0b1010 // Binary
// Floats
3.14
2.5e10
-1.5e-3
// Strings
"hello"
"line1\nline2" // Escape sequences
"value: ${expression}" // String interpolation
"multi
line
string"
// Characters
'a'
'\n'
'🎉'
// Booleans
true
false
// Unit
()
// Lists
[]
[1, 2, 3]
["a", "b", "c"]
// Records
{}
{ x: 1, y: 2 }
{ name: "Alice", age: 30 }
```
### Escape Sequences
| Sequence | Meaning |
|----------|---------|
| `\n` | Newline |
| `\t` | Tab |
| `\r` | Carriage return |
| `\\` | Backslash |
| `\"` | Double quote |
| `\'` | Single quote |
| `\$` | Dollar sign |
## Declarations
### Functions
```lux
// Basic function
fn name(param: Type): ReturnType = body
// Multiple parameters
fn add(a: Int, b: Int): Int = a + b
// Block body
fn complex(x: Int): Int = {
let y = x * 2
y + 1
}
// With effects
fn greet(name: String): Unit with {Console} =
Console.print("Hello, " + name)
// With type parameters
fn identity<T>(x: T): T = x
// With constraints
fn show<T>(x: T): String where T: Show = x.show()
// With behavioral properties
fn pure_add(a: Int, b: Int): Int is pure = a + b
// With visibility
pub fn exported(): Int = 42
// With documentation
/// Computes the factorial of n
pub fn factorial(n: Int): Int =
if n <= 1 then 1 else n * factorial(n - 1)
```
### Types
```lux
// Type alias
type UserId = Int
// Enum (sum type)
type Color =
| Red
| Green
| Blue
// With data
type Option<T> =
| Some(T)
| None
// With named fields
type Person =
| Person { name: String, age: Int }
// Record type
type Point = { x: Int, y: Int }
// With visibility
pub type PublicType = ...
```
### Effects
```lux
effect EffectName {
fn operation1(param: Type): ReturnType
fn operation2(): Type
}
// Example
effect Logger {
fn log(level: String, message: String): Unit
fn getLevel(): String
}
```
### Handlers
```lux
handler handlerName: EffectName {
fn operation1(param) = {
// implementation
resume(value)
}
}
// Example
handler consoleLogger: Logger {
fn log(level, msg) = {
Console.print("[" + level + "] " + msg)
resume(())
}
fn getLevel() = resume("debug")
}
```
### Traits
```lux
trait TraitName {
fn method(self): ReturnType
fn method2(self, param: Type): ReturnType
}
impl TraitName for Type {
fn method(self): ReturnType = ...
fn method2(self, param: Type): ReturnType = ...
}
```
### Let Bindings
```lux
// Top-level
let name = value
let name: Type = value
pub let exported = value
// Local (in blocks)
let x = 42
let (a, b) = (1, 2)
```
## Expressions
### Literals
See Lexical Structure above.
### Variables
```lux
x
myVariable
Some
```
### Function Application
```lux
f(x)
f(x, y, z)
moduleName.function(arg)
```
### Operators
```lux
// Arithmetic
a + b // Addition
a - b // Subtraction
a * b // Multiplication
a / b // Division
a % b // Remainder
-a // Negation
// Comparison
a == b // Equal
a != b // Not equal
a < b // Less than
a > b // Greater than
a <= b // Less or equal
a >= b // Greater or equal
// Boolean
a && b // And
a || b // Or
!a // Not
// String
s1 + s2 // Concatenation
```
### Conditionals
```lux
if condition then expr1 else expr2
if condition then
expr1
else
expr2
// Chained
if c1 then e1
else if c2 then e2
else e3
```
### Match Expressions
```lux
match value {
pattern1 => expr1,
pattern2 => expr2,
_ => default
}
// With guards
match value {
n if n > 0 => "positive",
n if n < 0 => "negative",
_ => "zero"
}
// Nested patterns
match expr {
Add(Num(0), x) => x,
Mul(x, Num(1)) => x,
_ => expr
}
```
### Blocks
```lux
{
statement1
statement2
result_expression
}
// Statements can be:
// - let bindings
// - expressions (for side effects)
```
### Lambda Expressions
```lux
fn(x: Type): ReturnType => body
// Examples
fn(x: Int): Int => x * 2
fn(a: Int, b: Int): Int => a + b
fn(): Unit => Console.print("hi")
```
### Run Expressions
```lux
run expr with {}
run expr with {
Effect1 = handler1,
Effect2 = handler2
}
// Example
run computation() with {
Logger = consoleLogger,
Database = mockDb
}
```
### Field Access
```lux
record.field
module.function
```
### Tuple Construction
```lux
(a, b)
(x, y, z)
```
### Record Construction
```lux
{ field1: value1, field2: value2 }
// Update
{ ...existing, field: newValue }
```
### List Construction
```lux
[]
[1, 2, 3]
[head, ...tail]
```
## Patterns
Used in match expressions and let bindings:
```lux
// Literal
42
"hello"
true
// Variable (binds the value)
x
name
// Wildcard (matches anything)
_
// Constructor
Some(x)
None
Pair(a, b)
// Tuple
(x, y)
(a, b, c)
// List
[]
[x]
[x, y]
[head, ...tail]
// Record
{ name, age }
{ name: n, age: a }
// With guard
x if x > 0
```
## Type Expressions
```lux
// Primitive
Int
Float
Bool
String
Char
Unit
// Named
TypeName
ModuleName.TypeName
// Generic application
Option<Int>
Result<String, Error>
List<T>
// Function
fn(Int): Int
fn(Int, String): Bool
fn(T): T with {Effect}
// Tuple
(Int, String)
(A, B, C)
// Record
{ x: Int, y: Int }
{ name: String, age: Int }
// List
List<Int>
```
## Modules
### Imports
```lux
// Full module
import path/to/module
// With alias
import path/to/module as alias
// Selective
import path/to/module.{item1, item2}
// Wildcard
import path/to/module.*
```
### Exports
```lux
// Public declarations
pub fn publicFunction(): Int = ...
pub type PublicType = ...
pub let publicValue = ...
// Private (default)
fn privateFunction(): Int = ...
```
## Grammar Summary
```
program = import* declaration*
import = 'import' path ('as' ident)?
| 'import' path '.{' ident (',' ident)* '}'
| 'import' path '.*'
declaration = function | type | effect | handler | trait | impl | let
function = 'pub'? 'fn' ident type_params? '(' params ')' ':' type
('with' '{' effects '}')? ('is' properties)?
('where' constraints)? '=' expr
type = 'pub'? 'type' ident type_params? '=' type_def
effect = 'effect' ident '{' operation* '}'
handler = 'handler' ident ':' ident '{' impl* '}'
expr = literal | ident | application | binary | unary
| if | match | block | lambda | run | field
pattern = literal | ident | '_' | constructor | tuple | list | record
```

125
docs/tutorials/README.md Normal file
View File

@@ -0,0 +1,125 @@
# Tutorials
Learn Lux by building real projects.
## Standard Programs
These tutorials cover common programming tasks:
| Tutorial | What You'll Build | Concepts |
|----------|-------------------|----------|
| [Calculator](calculator.md) | REPL calculator | Parsing, evaluation, REPL loop |
| [Todo App](todo.md) | CLI task manager | File I/O, data structures |
| [HTTP Client](http-client.md) | API consumer | HTTP effects, JSON parsing |
| [Word Counter](word-counter.md) | Text analyzer | File reading, string ops |
## Effect Showcases
These tutorials demonstrate Lux's unique effect system:
| Tutorial | What You'll Learn | Key Concept |
|----------|-------------------|-------------|
| [Dependency Injection](dependency-injection.md) | Testing with mock handlers | Handler swapping |
| [State Machines](state-machines.md) | Modeling state transitions | Custom effects |
| [Effects Cookbook](effects-cookbook.md) | Common effect patterns | Handler patterns |
## Quick Start: Your First Project
### 1. Create Project Directory
```bash
mkdir my-first-lux
cd my-first-lux
```
### 2. Create Main File
```lux
// main.lux
fn main(): Unit with {Console} = {
Console.print("Welcome to my Lux project!")
Console.print("Enter your name:")
let name = Console.readLine()
Console.print("Hello, " + name + "!")
}
let output = run main() with {}
```
### 3. Run It
```bash
lux main.lux
```
### 4. Add a Module
```lux
// lib/greetings.lux
pub fn hello(name: String): String =
"Hello, " + name + "!"
pub fn goodbye(name: String): String =
"Goodbye, " + name + "!"
```
```lux
// main.lux
import lib/greetings as greet
fn main(): Unit with {Console} = {
Console.print("Enter your name:")
let name = Console.readLine()
Console.print(greet.hello(name))
Console.print(greet.goodbye(name))
}
let output = run main() with {}
```
## Project Ideas by Difficulty
### Beginner
- **Temperature converter** - Convert between Celsius, Fahrenheit, Kelvin
- **Number guessing game** - Random number with hints
- **Simple quiz** - Multiple choice questions with scoring
- **Unit converter** - Length, weight, volume conversions
### Intermediate
- **Markdown previewer** - Parse basic Markdown to HTML
- **Contact book** - CRUD with file persistence
- **Simple grep** - Search files for patterns
- **CSV processor** - Read, filter, transform CSV files
### Advanced
- **Test framework** - Use effects for test isolation
- **Config loader** - Effect-based configuration with validation
- **Mini interpreter** - Build a small language
- **Chat client** - HTTP-based chat application
### Effect Showcases
- **Transaction system** - Rollback on failure
- **Capability security** - Effects as capabilities
- **Async simulation** - Model async with effects
- **Dependency graph** - Track and inject dependencies
## How to Use These Tutorials
1. **Read through first** - Understand the goal
2. **Type the code** - Don't copy-paste
3. **Experiment** - Modify and see what happens
4. **Build your own** - Apply concepts to your ideas
## Getting Help
- Check the [Language Reference](../reference/syntax.md)
- See [examples/](../../examples/) for working code
- Use the REPL to experiment
Happy building!

View File

@@ -0,0 +1,251 @@
# Tutorial: Building a Calculator
Build a REPL calculator that evaluates arithmetic expressions.
## What You'll Learn
- Defining algebraic data types
- Pattern matching
- Recursive functions
- REPL loops with effects
## The Goal
```
Calculator REPL
> 2 + 3
5
> (10 - 4) * 2
12
> 100 / 5 + 3
23
> quit
Goodbye!
```
## Step 1: Define the Expression Type
First, we model arithmetic expressions:
```lux
// calculator.lux
type Expr =
| Num(Int)
| Add(Expr, Expr)
| Sub(Expr, Expr)
| Mul(Expr, Expr)
| Div(Expr, Expr)
```
This is an *algebraic data type* (ADT). An `Expr` is one of:
- A number: `Num(42)`
- An addition: `Add(Num(2), Num(3))`
- And so on...
## Step 2: Evaluate Expressions
Now we evaluate expressions to integers:
```lux
fn eval(e: Expr): Result<Int, String> =
match e {
Num(n) => Ok(n),
Add(a, b) => evalBinOp(a, b, fn(x: Int, y: Int): Int => x + y),
Sub(a, b) => evalBinOp(a, b, fn(x: Int, y: Int): Int => x - y),
Mul(a, b) => evalBinOp(a, b, fn(x: Int, y: Int): Int => x * y),
Div(a, b) => {
match (eval(a), eval(b)) {
(Ok(x), Ok(0)) => Err("Division by zero"),
(Ok(x), Ok(y)) => Ok(x / y),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
}
}
fn evalBinOp(a: Expr, b: Expr, op: fn(Int, Int): Int): Result<Int, String> =
match (eval(a), eval(b)) {
(Ok(x), Ok(y)) => Ok(op(x, y)),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
```
Pattern matching makes this clear:
- `Num(n)` just returns the number
- Operations evaluate both sides, then apply the operator
- Division checks for zero
## Step 3: Parse Expressions
For simplicity, we'll parse a limited format. A real parser would be more complex.
```lux
fn parseSimple(input: String): Result<Expr, String> = {
let trimmed = String.trim(input)
// Try to parse as a number
if isNumber(trimmed) then
Ok(Num(parseInt(trimmed)))
else
// Try to find an operator
match findOperator(trimmed) {
Some((left, op, right)) => {
match (parseSimple(left), parseSimple(right)) {
(Ok(l), Ok(r)) => Ok(makeOp(l, op, r)),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e)
}
},
None => Err("Cannot parse: " + trimmed)
}
}
fn isNumber(s: String): Bool = {
let chars = String.chars(s)
List.all(chars, fn(c: String): Bool =>
c == "-" || (c >= "0" && c <= "9")
)
}
fn parseInt(s: String): Int = {
// Simplified - assumes valid integer
List.fold(String.chars(s), 0, fn(acc: Int, c: String): Int =>
if c == "-" then acc
else acc * 10 + charToDigit(c)
) * (if String.startsWith(s, "-") then -1 else 1)
}
fn charToDigit(c: String): Int =
match c {
"0" => 0, "1" => 1, "2" => 2, "3" => 3, "4" => 4,
"5" => 5, "6" => 6, "7" => 7, "8" => 8, "9" => 9,
_ => 0
}
fn findOperator(s: String): Option<(String, String, String)> = {
// Find last +/- at depth 0 (lowest precedence)
// Then *// (higher precedence)
// This is a simplified approach
let addSub = findOpAtDepth(s, ["+", "-"], 0)
match addSub {
Some(r) => Some(r),
None => findOpAtDepth(s, ["*", "/"], 0)
}
}
fn makeOp(left: Expr, op: String, right: Expr): Expr =
match op {
"+" => Add(left, right),
"-" => Sub(left, right),
"*" => Mul(left, right),
"/" => Div(left, right),
_ => Num(0) // Should not happen
}
```
## Step 4: The REPL Loop
Now we build the interactive loop:
```lux
fn repl(): Unit with {Console} = {
Console.print("Calculator REPL (type 'quit' to exit)")
replLoop()
}
fn replLoop(): Unit with {Console} = {
Console.print("> ")
let input = Console.readLine()
if input == "quit" then
Console.print("Goodbye!")
else {
match parseSimple(input) {
Ok(expr) => {
match eval(expr) {
Ok(result) => Console.print(toString(result)),
Err(e) => Console.print("Error: " + e)
}
},
Err(e) => Console.print("Parse error: " + e)
}
replLoop() // Continue the loop
}
}
fn main(): Unit with {Console} = repl()
let output = run main() with {}
```
## Step 5: Test It
```bash
$ lux calculator.lux
Calculator REPL (type 'quit' to exit)
> 2 + 3
5
> 10 - 4
6
> 6 * 7
42
> 100 / 0
Error: Division by zero
> quit
Goodbye!
```
## Extending the Calculator
Try adding:
1. **Parentheses**: Parse `(2 + 3) * 4`
2. **Variables**: Store results in named variables
3. **Functions**: `sqrt`, `abs`, `pow`
4. **History**: Use State effect to track previous results
### Adding Variables (Bonus)
```lux
effect Variables {
fn get(name: String): Option<Int>
fn set(name: String, value: Int): Unit
}
fn evalWithVars(e: Expr): Result<Int, String> with {Variables} =
match e {
Var(name) => {
match Variables.get(name) {
Some(v) => Ok(v),
None => Err("Unknown variable: " + name)
}
},
// ... other cases
}
handler memoryVars: Variables {
fn get(name) = resume(State.get(name))
fn set(name, value) = {
State.put(name, value)
resume(())
}
}
```
## Complete Code
See `examples/tutorials/calculator.lux` for the full implementation.
## What You Learned
- **ADTs** model structured data
- **Pattern matching** destructures data cleanly
- **Recursion** processes nested structures
- **Result** handles errors without exceptions
- **REPL loops** combine effects naturally
## Next Tutorial
[Todo App](todo.md) - Build a task manager with file persistence.

View File

@@ -0,0 +1,380 @@
# Tutorial: Dependency Injection with Effects
Learn how effects provide natural dependency injection for testing and flexibility.
## What You'll Learn
- Using effects for dependencies
- Swapping handlers for testing
- The "ports and adapters" pattern
## The Problem
Imagine you're building a user registration system:
```javascript
// Traditional approach (pseudo-code)
function registerUser(userData) {
const validated = validate(userData);
database.insert(validated); // Hard-coded dependency
emailService.send(validated.email, "Welcome!"); // Another one
logger.info("User registered"); // And another
return validated.id;
}
```
Testing this requires mocking `database`, `emailService`, and `logger`. In many languages, this means:
- Dependency injection frameworks
- Mock libraries
- Complex test setup
## The Lux Way
In Lux, dependencies are effects:
```lux
// Define what we need (not how it works)
effect Database {
fn insert(table: String, data: String): Int
fn query(sql: String): List<String>
}
effect Email {
fn send(to: String, subject: String, body: String): Unit
}
effect Logger {
fn info(message: String): Unit
fn error(message: String): Unit
}
```
Now our business logic declares its dependencies:
```lux
type User = { name: String, email: String }
fn registerUser(user: User): Int with {Database, Email, Logger} = {
Logger.info("Registering user: " + user.name)
// Validate
if user.name == "" then {
Logger.error("Empty name")
Fail.fail("Name required")
} else ()
if user.email == "" then {
Logger.error("Empty email")
Fail.fail("Email required")
} else ()
// Insert into database
let userId = Database.insert("users", userToJson(user))
Logger.info("Created user with ID: " + toString(userId))
// Send welcome email
Email.send(user.email, "Welcome!", "Thanks for registering, " + user.name)
Logger.info("Sent welcome email")
userId
}
fn userToJson(user: User): String =
"{\"name\":\"" + user.name + "\",\"email\":\"" + user.email + "\"}"
```
## Production Handlers
For production, we implement real handlers:
```lux
handler postgresDb: Database {
fn insert(table, data) = {
let result = Http.post("http://db-service/insert", data)
// Parse ID from response
resume(parseId(result))
}
fn query(sql) = {
let result = Http.post("http://db-service/query", sql)
resume(parseRows(result))
}
}
handler smtpEmail: Email {
fn send(to, subject, body) = {
Http.post("http://email-service/send", formatEmail(to, subject, body))
resume(())
}
}
handler consoleLogger: Logger {
fn info(msg) = {
Console.print("[INFO] " + msg)
resume(())
}
fn error(msg) = {
Console.print("[ERROR] " + msg)
resume(())
}
}
```
Production code:
```lux
fn main(): Unit with {Console, Http} = {
let user = User { name: "Alice", email: "alice@example.com" }
let result = run registerUser(user) with {
Database = postgresDb,
Email = smtpEmail,
Logger = consoleLogger
}
Console.print("Registered user ID: " + toString(result))
}
```
## Test Handlers
For testing, we swap in mock handlers:
```lux
// Mock database that stores in memory
handler mockDb: Database {
fn insert(table, data) = {
let id = State.get()
State.put(id + 1)
Console.print("[MOCK DB] Inserted into " + table + ": " + data)
resume(id)
}
fn query(sql) = {
Console.print("[MOCK DB] Query: " + sql)
resume(["mock", "data"])
}
}
// Mock email that just logs
handler mockEmail: Email {
fn send(to, subject, body) = {
Console.print("[MOCK EMAIL] To: " + to + ", Subject: " + subject)
resume(())
}
}
// Silent logger for tests
handler silentLogger: Logger {
fn info(msg) = resume(())
fn error(msg) = resume(())
}
// Collecting logger for assertions
handler collectingLogger: Logger {
fn info(msg) = {
State.put(State.get() + "[INFO] " + msg + "\n")
resume(())
}
fn error(msg) = {
State.put(State.get() + "[ERROR] " + msg + "\n")
resume(())
}
}
```
Test code:
```lux
fn testRegisterUser(): Unit with {Console} = {
let user = User { name: "Test", email: "test@test.com" }
// Run with mocks
let (userId, logs) = run {
let id = run {
run registerUser(user) with {
Database = mockDb,
Email = mockEmail,
Logger = collectingLogger
}
} with { State = 1 } // Database ID counter
let logs = State.get()
(id, logs)
} with { State = "" } // Log accumulator
// Assertions
Console.print("User ID: " + toString(userId))
Console.print("Logs:\n" + logs)
if userId == 1 then
Console.print("✓ User ID is correct")
else
Console.print("✗ User ID is wrong")
if String.contains(logs, "Registering user") then
Console.print("✓ Registration was logged")
else
Console.print("✗ Registration was not logged")
}
```
## Testing Failure Cases
Test error handling by using handlers that fail:
```lux
handler failingDb: Database {
fn insert(table, data) = {
Fail.fail("Database connection failed")
}
fn query(sql) = resume([])
}
fn testDatabaseFailure(): Unit with {Console} = {
let user = User { name: "Test", email: "test@test.com" }
let result = run {
run registerUser(user) with {
Database = failingDb,
Email = mockEmail,
Logger = silentLogger
}
} with {}
Console.print("Test: Database failure is handled")
// The Fail effect should have triggered
}
```
## The "Ports and Adapters" Pattern
This is also known as hexagonal architecture:
```
┌─────────────────────┐
│ Business Logic │
│ (Pure + Effects) │
└─────────────────────┘
┌───────────────┼───────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Database │ │ Email │ │ Logger │
│ Effect │ │ Effect │ │ Effect │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
┌─────────┼─────────┐ │ ┌─────┼─────┐
│ │ │ │ │ │
┌───┴───┐ ┌───┴───┐ │ ┌───┴───┐ ┌───┴───┐ ┌─────┴─────┐
│Postgres│ │MockDB │ │ │ SMTP │ │Console│ │ Collector │
└───────┘ └───────┘ │ └───────┘ └───────┘ └───────────┘
┌─────┴─────┐
│ MockEmail │
└───────────┘
```
- **Business logic** is pure (plus declared effects)
- **Effects** are the ports (interfaces)
- **Handlers** are the adapters (implementations)
## Benefits
1. **No mocking libraries needed** - Just write a different handler
2. **Type-safe** - The compiler ensures all effects are handled
3. **Explicit dependencies** - You can see what a function needs
4. **Easy to test** - Swap handlers, no reflection or magic
5. **Flexible** - Same code, different environments
## Complete Example
```lux
// main.lux
// === Effects (Ports) ===
effect Database {
fn insert(table: String, data: String): Int
}
effect Email {
fn send(to: String, body: String): Unit
}
// === Business Logic ===
type User = { name: String, email: String }
fn registerUser(user: User): Int with {Database, Email, Fail} = {
if user.name == "" then Fail.fail("Name required") else ()
let id = Database.insert("users", user.name)
Email.send(user.email, "Welcome!")
id
}
// === Production Handlers (Adapters) ===
handler prodDb: Database {
fn insert(table, data) = {
Console.print("[DB] INSERT INTO " + table)
resume(42) // Would be real ID
}
}
handler prodEmail: Email {
fn send(to, body) = {
Console.print("[EMAIL] Sending to " + to)
resume(())
}
}
// === Test Handlers ===
handler testDb: Database {
fn insert(table, data) = resume(1)
}
handler testEmail: Email {
fn send(to, body) = resume(())
}
// === Running ===
fn production(): Unit with {Console} = {
let user = User { name: "Alice", email: "alice@example.com" }
let id = run registerUser(user) with {
Database = prodDb,
Email = prodEmail
}
Console.print("Created user: " + toString(id))
}
fn test(): Unit with {Console} = {
let user = User { name: "Test", email: "test@test.com" }
let id = run registerUser(user) with {
Database = testDb,
Email = testEmail
}
if id == 1 then Console.print("✓ Test passed")
else Console.print("✗ Test failed")
}
fn main(): Unit with {Console} = {
Console.print("=== Production ===")
production()
Console.print("\n=== Test ===")
test()
}
let output = run main() with {}
```
## What You Learned
- Effects define **what** you need, handlers define **how**
- Swap handlers for testing without changing business logic
- No dependency injection framework needed
- Type system ensures all dependencies are satisfied
## Next Tutorial
[State Machines](state-machines.md) - Model state transitions with custom effects.

View File

@@ -0,0 +1,325 @@
# Project Ideas
Here are projects to build with Lux, organized by difficulty and purpose.
## Beginner Projects
### 1. Temperature Converter
Convert between Celsius, Fahrenheit, and Kelvin.
**Skills**: Basic I/O, functions, conditionals
```lux
// Starter code
fn celsiusToFahrenheit(c: Float): Float = c * 9.0 / 5.0 + 32.0
fn fahrenheitToCelsius(f: Float): Float = (f - 32.0) * 5.0 / 9.0
fn main(): Unit with {Console} = {
Console.print("Temperature Converter")
Console.print("1. Celsius to Fahrenheit")
Console.print("2. Fahrenheit to Celsius")
// ... implement menu and conversion
}
```
### 2. Number Guessing Game
Computer picks a number, user guesses with hints.
**Skills**: Random effect, loops, conditionals
```lux
fn game(): Unit with {Console, Random} = {
let secret = Random.int(1, 100)
Console.print("I'm thinking of a number 1-100...")
guessLoop(secret, 1)
}
```
### 3. Word Counter
Count words, lines, and characters in a file.
**Skills**: File effect, string operations
```lux
fn countFile(path: String): Unit with {Console, File} = {
let content = File.read(path)
let lines = String.lines(content)
let words = countWords(content)
let chars = String.length(content)
// ... display results
}
```
### 4. Simple Quiz
Multiple choice questions with scoring.
**Skills**: ADTs, pattern matching, state
```lux
type Question = { text: String, options: List<String>, correct: Int }
fn askQuestion(q: Question): Bool with {Console} = {
Console.print(q.text)
// ... display options and check answer
}
```
---
## Intermediate Projects
### 5. Contact Book
CRUD operations with file persistence.
**Skills**: File I/O, JSON, ADTs, effects
```lux
type Contact = { name: String, email: String, phone: String }
effect ContactStore {
fn add(contact: Contact): Int
fn find(name: String): Option<Contact>
fn list(): List<Contact>
fn delete(id: Int): Bool
}
// Implement handlers for file-based and in-memory storage
```
### 6. Markdown Parser
Parse basic Markdown to HTML.
**Skills**: Parsing, string manipulation, ADTs
```lux
type MarkdownNode =
| Heading(Int, String) // level, text
| Paragraph(String)
| Bold(String)
| Italic(String)
| Code(String)
| List(List<String>)
| Link(String, String) // text, url
fn parseMarkdown(input: String): List<MarkdownNode> = ...
fn toHtml(nodes: List<MarkdownNode>): String = ...
```
### 7. Simple HTTP API Client
Fetch and display data from a REST API.
**Skills**: HTTP effect, JSON parsing
```lux
fn fetchWeather(city: String): Unit with {Console, Http} = {
let response = Http.get("https://api.weather.com/city/" + city)
let data = Json.parse(response)
let temp = Json.getFloat(data, "temperature")
Console.print(city + ": " + toString(temp) + "°C")
}
```
### 8. File Backup Tool
Copy files with logging and error handling.
**Skills**: File effect, error handling, recursion
```lux
fn backup(source: String, dest: String): Unit with {File, Console, Fail} = {
if File.isDirectory(source) then
backupDirectory(source, dest)
else
backupFile(source, dest)
}
```
---
## Advanced Projects
### 9. Effect-Based Test Framework
Use effects for test isolation and assertions.
**Skills**: Custom effects, handlers, composition
```lux
effect Assert {
fn equal<T>(actual: T, expected: T): Unit
fn true(condition: Bool): Unit
fn fail(message: String): Unit
}
effect Test {
fn describe(name: String, tests: fn(): Unit): Unit
fn it(name: String, test: fn(): Unit): Unit
}
// Handlers collect results, run tests, report
```
### 10. Configuration DSL
Type-safe configuration with validation.
**Skills**: Effects, validation, ADTs
```lux
effect Config {
fn required(key: String): String
fn optional(key: String, default: String): String
fn validate(key: String, validator: fn(String): Bool): String
}
fn loadAppConfig(): AppConfig with {Config, Fail} = {
AppConfig {
host: Config.required("HOST"),
port: Config.validate("PORT", isValidPort),
debug: Config.optional("DEBUG", "false") == "true"
}
}
```
### 11. Mini Language Interpreter
Build an interpreter for a simple language.
**Skills**: Parsing, ADTs, recursion, effects
```lux
type Expr = ...
type Stmt = ...
type Value = ...
effect Runtime {
fn getVar(name: String): Value
fn setVar(name: String, value: Value): Unit
fn print(value: Value): Unit
}
fn interpret(program: List<Stmt>): Unit with {Runtime} = ...
```
### 12. Task Scheduler
Schedule and run tasks with dependencies.
**Skills**: Graphs, effects, async simulation
```lux
type Task = { id: String, deps: List<String>, action: fn(): Unit }
effect Scheduler {
fn schedule(task: Task): Unit
fn run(): Unit
fn wait(taskId: String): Unit
}
```
---
## Effect Showcase Projects
These projects specifically highlight Lux's effect system.
### 13. Transactional Operations
Rollback on failure using effects.
```lux
effect Transaction {
fn begin(): Unit
fn commit(): Unit
fn rollback(): Unit
}
fn transfer(from: Account, to: Account, amount: Int): Unit
with {Transaction, Database, Fail} = {
Transaction.begin()
Database.debit(from, amount)
Database.credit(to, amount) // If this fails, rollback
Transaction.commit()
}
```
### 14. Mock HTTP for Testing
Swap real HTTP with recorded responses.
```lux
handler recordedHttp(responses: Map<String, String>): Http {
fn get(url) = {
match Map.get(responses, url) {
Some(body) => resume(body),
None => Fail.fail("No recorded response for: " + url)
}
}
}
// Test with recorded responses
let testResponses = Map.from([
("https://api.example.com/users", "[{\"id\": 1}]")
])
run fetchUsers() with { Http = recordedHttp(testResponses) }
```
### 15. Capability-Based Security
Use effects as capabilities.
```lux
effect FileRead { fn read(path: String): String }
effect FileWrite { fn write(path: String, content: String): Unit }
effect Network { fn fetch(url: String): String }
// This function can ONLY read files - it cannot write or use network
fn processConfig(path: String): Config with {FileRead} = ...
// This function has network but no file access
fn fetchData(url: String): Data with {Network} = ...
```
### 16. Async Simulation
Model async operations with effects.
```lux
effect Async {
fn spawn(task: fn(): T): Future<T>
fn await(future: Future<T>): T
fn sleep(ms: Int): Unit
}
fn parallel(): List<Int> with {Async} = {
let f1 = Async.spawn(fn(): Int => compute1())
let f2 = Async.spawn(fn(): Int => compute2())
let f3 = Async.spawn(fn(): Int => compute3())
[Async.await(f1), Async.await(f2), Async.await(f3)]
}
```
---
## Project Complexity Guide
| Project | Effects Used | Lines of Code | Time |
|---------|--------------|---------------|------|
| Temperature Converter | Console | ~50 | 1 hour |
| Guessing Game | Console, Random | ~80 | 2 hours |
| Word Counter | Console, File | ~60 | 1 hour |
| Contact Book | Console, File, Custom | ~200 | 4 hours |
| Markdown Parser | Pure + Console | ~300 | 6 hours |
| Test Framework | Custom effects | ~400 | 8 hours |
| Mini Interpreter | Custom effects | ~600 | 16 hours |
---
## Getting Started
1. **Pick a project** at your skill level
2. **Break it down** into smaller tasks
3. **Start with types** - define your data structures
4. **Add effects** - what I/O do you need?
5. **Implement logic** - write pure functions first
6. **Test with handlers** - swap in mock handlers
## Need Help?
- Check `examples/` for working code
- Read the [Effects Guide](../guide/05-effects.md)
- Experiment in the REPL
Happy building!