feat: rebuild website with full learning funnel

Website rebuilt from scratch based on analysis of 11 beloved language
websites (Elm, Zig, Gleam, Swift, Kotlin, Haskell, OCaml, Crystal, Roc,
Rust, Go).

New website structure:
- Homepage with hero, playground, three pillars, install guide
- Language Tour with interactive lessons (hello world, types, effects)
- Examples cookbook with categorized sidebar
- API documentation index
- Installation guide (Nix and source)
- Sleek/noble design (black/gold, serif typography)

Also includes:
- New stdlib/json.lux module for JSON serialization
- Enhanced stdlib/http.lux with middleware and routing
- New string functions (charAt, indexOf, lastIndexOf, repeat)
- LSP improvements (rename, signature help, formatting)
- Package manager transitive dependency resolution
- Updated documentation for effects and stdlib
- New showcase example (task_manager.lux)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 23:05:35 -05:00
parent 5a853702d1
commit 7e76acab18
44 changed files with 12468 additions and 3354 deletions

107
examples/showcase/README.md Normal file
View File

@@ -0,0 +1,107 @@
# Task Manager Showcase
This example demonstrates Lux's three killer features in a practical, real-world context.
## Running the Example
```bash
lux run examples/showcase/task_manager.lux
```
## Features Demonstrated
### 1. Algebraic Effects
Every function signature shows exactly what side effects it can perform:
```lux
fn createTask(title: String, priority: String): Task@latest
with {TaskStore, Random} = { ... }
```
- `TaskStore` - database operations
- `Random` - random number generation
- No hidden I/O or surprise calls
### 2. Behavioral Types
Compile-time guarantees about function behavior:
```lux
fn formatTask(task: Task@latest): String
is pure // No side effects
is deterministic // Same input = same output
is total // Always terminates
```
```lux
fn completeTask(id: String): Option<Task@latest>
is idempotent // Safe to retry
with {TaskStore}
```
### 3. Schema Evolution
Versioned types with automatic migration:
```lux
type Task @v2 {
id: String,
title: String,
done: Bool,
priority: String, // New in v2
from @v1 = { ...old, priority: "medium" }
}
```
### 4. Handler Swapping (Testing)
Test without mocks by swapping effect handlers:
```lux
// Production
run processOrders() with {
TaskStore = PostgresTaskStore,
Logger = CloudLogger
}
// Testing
run processOrders() with {
TaskStore = InMemoryTaskStore,
Logger = SilentLogger
}
```
## Why This Matters
| Traditional Languages | Lux |
|----------------------|-----|
| Side effects are implicit | Effects in type signatures |
| Runtime crashes | Compile-time verification |
| Complex mocking frameworks | Simple handler swapping |
| Manual migration code | Automatic schema evolution |
| Hope for retry safety | Verified idempotency |
## File Structure
```
showcase/
├── README.md # This file
└── task_manager.lux # Main example with all features
```
## Key Sections in the Code
1. **Versioned Data Types** - `Task @v1`, `@v2`, `@v3` with migrations
2. **Pure Functions** - `is pure`, `is total`, `is deterministic`, `is idempotent`
3. **Effects** - `effect TaskStore` and `effect Logger`
4. **Effect Handlers** - `InMemoryTaskStore`, `ConsoleLogger`
5. **Testing** - `runTestScenario()` with swapped handlers
6. **Migration Demo** - `demonstrateMigration()`
## Next Steps
- Read the [Behavioral Types Guide](../../docs/guide/12-behavioral-types.md)
- Read the [Schema Evolution Guide](../../docs/guide/13-schema-evolution.md)
- Explore [more examples](../)

View File

@@ -0,0 +1,419 @@
// =============================================================================
// Task Manager API - A Showcase of Lux's Unique Features
// =============================================================================
//
// This example demonstrates Lux's three killer features:
//
// 1. ALGEBRAIC EFFECTS - Every side effect is explicit in function signatures
// - No hidden I/O, no surprise database calls
// - Testing is trivial: just swap handlers
//
// 2. BEHAVIORAL TYPES - Compile-time guarantees about function behavior
// - `is pure` - no side effects, safe to cache
// - `is total` - always terminates, never fails
// - `is idempotent` - safe to retry without side effects
// - `is deterministic` - same input = same output
//
// 3. SCHEMA EVOLUTION - Versioned types with automatic migration
// - Data structures evolve safely over time
// - Old data automatically upgrades
//
// To run: lux run examples/showcase/task_manager.lux
// =============================================================================
// =============================================================================
// PART 1: VERSIONED DATA TYPES (Schema Evolution)
// =============================================================================
// Task v1: Our original data model (simple)
type Task @v1 {
id: String,
title: String,
done: Bool
}
// Task v2: Added priority field
// The `from @v1` clause defines how to migrate old data automatically
type Task @v2 {
id: String,
title: String,
done: Bool,
priority: String, // New field: "low", "medium", "high"
// Migration: old tasks get "medium" priority by default
from @v1 = {
id: old.id,
title: old.title,
done: old.done,
priority: "medium"
}
}
// Task v3: Added due date and tags
// Migrations chain automatically: v1 → v2 → v3
type Task @v3 {
id: String,
title: String,
done: Bool,
priority: String,
dueDate: Option<Int>, // Unix timestamp, optional
tags: List<String>, // New: categorization
from @v2 = {
id: old.id,
title: old.title,
done: old.done,
priority: old.priority,
dueDate: None, // No due date for migrated tasks
tags: [] // Empty tags for migrated tasks
}
}
// Use @latest to always refer to the newest version
type TaskList = List<Task@latest>
// =============================================================================
// PART 2: PURE FUNCTIONS WITH BEHAVIORAL TYPES
// =============================================================================
// Pure function: no side effects, safe to cache, parallelize, eliminate if unused
// The compiler verifies `is pure` - if you try to call an effect, it errors.
fn formatTask(task: Task@latest): String
is pure
is deterministic
is total = {
let status = if task.done then "[x]" else "[ ]"
let priority = match task.priority {
"high" => "!!",
"medium" => "!",
_ => ""
}
status + " " + priority + task.title
}
// Idempotent function: f(f(x)) = f(x)
// Safe to apply multiple times without changing the result
// Critical for retry logic - the compiler verifies this property
fn normalizeTitle(title: String): String
is pure
is idempotent = {
title
|> String.trim
|> String.toLower
}
// Total function: always terminates, never throws
// No Fail effect allowed, recursion must be structurally decreasing
fn countCompleted(tasks: TaskList): Int
is pure
is total = {
match tasks {
[] => 0,
[task, ...rest] =>
(if task.done then 1 else 0) + countCompleted(rest)
}
}
// Commutative function: f(a, b) = f(b, a)
// Enables parallel reduction and argument reordering optimizations
fn maxPriority(a: String, b: String): String
is pure
is commutative = {
let priorityValue = fn(p: String): Int =>
match p {
"high" => 3,
"medium" => 2,
"low" => 1,
_ => 0
}
if priorityValue(a) > priorityValue(b) then a else b
}
// Filter tasks by criteria - pure, can be cached and parallelized
fn filterByPriority(tasks: TaskList, priority: String): TaskList
is pure
is deterministic = {
List.filter(tasks, fn(t: Task@latest): Bool => t.priority == priority)
}
fn filterPending(tasks: TaskList): TaskList
is pure
is deterministic = {
List.filter(tasks, fn(t: Task@latest): Bool => !t.done)
}
fn filterCompleted(tasks: TaskList): TaskList
is pure
is deterministic = {
List.filter(tasks, fn(t: Task@latest): Bool => t.done)
}
// =============================================================================
// PART 3: EFFECTS - EXPLICIT SIDE EFFECTS
// =============================================================================
// Custom effect for task storage
// This declares WHAT operations are available, not HOW they work
effect TaskStore {
fn save(task: Task@latest): Result<Task@latest, String>
fn getById(id: String): Option<Task@latest>
fn getAll(): TaskList
fn delete(id: String): Bool
}
// Service functions declare their effects in the type signature
// Anyone reading the signature knows exactly what side effects can occur
// Create a new task - requires TaskStore and Random effects
fn createTask(title: String, priority: String): Task@latest
with {TaskStore, Random} = {
let id = "task_" + toString(Random.int(10000, 99999))
let task = {
id: id,
title: normalizeTitle(title), // Uses our idempotent normalizer
done: false,
priority: priority,
dueDate: None,
tags: []
}
match TaskStore.save(task) {
Ok(saved) => saved,
Err(_) => task // Return unsaved if storage fails
}
}
// Complete a task - idempotent, safe to retry
// If the network fails mid-request, retry is safe
fn completeTask(id: String): Option<Task@latest>
is idempotent // Compiler verifies this is safe to retry
with {TaskStore} = {
match TaskStore.getById(id) {
None => None,
Some(task) => {
// Setting done = true is idempotent: already done? stays done
let updated = { ...task, done: true }
match TaskStore.save(updated) {
Ok(saved) => Some(saved),
Err(_) => None
}
}
}
}
// Get task summary - logging effect, but computation is pure
fn getTaskSummary(): { total: Int, completed: Int, pending: Int, highPriority: Int }
with {TaskStore, Logger} = {
let tasks = TaskStore.getAll()
Logger.log("Fetched " + toString(List.length(tasks)) + " tasks")
// These computations are pure - could be parallelized
let completed = countCompleted(tasks)
let pending = List.length(tasks) - completed
let highPriority = List.length(filterByPriority(tasks, "high"))
{ total: List.length(tasks), completed: completed, pending: pending, highPriority: highPriority }
}
// =============================================================================
// PART 4: EFFECT HANDLERS - SWAP IMPLEMENTATIONS
// =============================================================================
// In-memory handler for testing
// This handler stores tasks in a mutable list - perfect for unit tests
handler InMemoryTaskStore: TaskStore {
let tasks: List<Task@latest> = []
fn save(task: Task@latest): Result<Task@latest, String> = {
// Remove existing task with same ID (if any), then add new
tasks = List.filter(tasks, fn(t: Task@latest): Bool => t.id != task.id)
tasks = List.concat(tasks, [task])
Ok(task)
}
fn getById(id: String): Option<Task@latest> = {
List.find(tasks, fn(t: Task@latest): Bool => t.id == id)
}
fn getAll(): TaskList = tasks
fn delete(id: String): Bool = {
let before = List.length(tasks)
tasks = List.filter(tasks, fn(t: Task@latest): Bool => t.id != task.id)
List.length(tasks) < before
}
}
// Logging handler - wraps another handler with logging
handler LoggingTaskStore(inner: TaskStore): TaskStore with {Logger} {
fn save(task: Task@latest): Result<Task@latest, String> = {
Logger.log("Saving task: " + task.id)
inner.save(task)
}
fn getById(id: String): Option<Task@latest> = {
Logger.log("Getting task: " + id)
inner.getById(id)
}
fn getAll(): TaskList = {
Logger.log("Getting all tasks")
inner.getAll()
}
fn delete(id: String): Bool = {
Logger.log("Deleting task: " + id)
inner.delete(id)
}
}
// Simple logger effect and handler
effect Logger {
fn log(message: String): Unit
}
handler ConsoleLogger: Logger with {Console} {
fn log(message: String): Unit = {
Console.print("[LOG] " + message)
}
}
handler SilentLogger: Logger {
fn log(message: String): Unit = {
// Do nothing - useful for tests
}
}
// =============================================================================
// PART 5: TESTING - SWAP HANDLERS, NO MOCKS NEEDED
// =============================================================================
// Test helper: creates a controlled environment
fn runTestScenario(): Unit with {Console} = {
Console.print("=== Running Test Scenario ===")
Console.print("")
// Use in-memory storage and silent logging for tests
// No database, no file I/O, no network - pure in-memory testing
let result = run {
// Create some tasks
let task1 = createTask("Write documentation", "high")
let task2 = createTask("Fix bug #123", "medium")
let task3 = createTask("Review PR", "low")
// Complete one task
completeTask(task1.id)
// Get summary
getTaskSummary()
} with {
TaskStore = InMemoryTaskStore,
Logger = SilentLogger,
Random = {
// Deterministic "random" for tests
let counter = 0
fn int(min: Int, max: Int): Int = {
counter = counter + 1
min + (counter * 12345) % (max - min)
}
}
}
Console.print("Test Results:")
Console.print(" Total tasks: " + toString(result.total))
Console.print(" Completed: " + toString(result.completed))
Console.print(" Pending: " + toString(result.pending))
Console.print(" High priority: " + toString(result.highPriority))
Console.print("")
// Verify results
if result.total == 3 &&
result.completed == 1 &&
result.pending == 2 &&
result.highPriority == 1 {
Console.print("All tests passed!")
} else {
Console.print("Test failed!")
}
}
// =============================================================================
// PART 6: SCHEMA MIGRATION DEMO
// =============================================================================
fn demonstrateMigration(): Unit with {Console} = {
Console.print("=== Schema Evolution Demo ===")
Console.print("")
// Simulate loading a v1 task (from old database/API)
let oldTask = Schema.versioned("Task", 1, {
id: "legacy_001",
title: "Old task from v1",
done: false
})
Console.print("Loaded v1 task:")
Console.print(" Version: " + toString(Schema.getVersion(oldTask)))
Console.print("")
// Migrate to latest version automatically
let migratedTask = Schema.migrate(oldTask, 3)
Console.print("After migration to v3:")
Console.print(" Version: " + toString(Schema.getVersion(migratedTask)))
Console.print(" Has priority: " + migratedTask.priority) // Added by v2 migration
Console.print(" Has tags: " + toString(List.length(migratedTask.tags)) + " tags") // Added by v3
Console.print("")
Console.print("Old data seamlessly upgraded!")
}
// =============================================================================
// PART 7: MAIN - PUTTING IT ALL TOGETHER
// =============================================================================
fn main(): Unit with {Console} = {
Console.print("╔═══════════════════════════════════════════════════════════╗")
Console.print("║ Lux Task Manager - Feature Showcase ║")
Console.print("╚═══════════════════════════════════════════════════════════╝")
Console.print("")
// Demonstrate pure functions
Console.print("--- Pure Functions (Behavioral Types) ---")
let sampleTask = {
id: "demo",
title: "Learn Lux",
done: false,
priority: "high",
dueDate: None,
tags: ["learning", "programming"]
}
Console.print("Formatted task: " + formatTask(sampleTask))
Console.print("Normalized title: " + normalizeTitle(" HELLO WORLD "))
Console.print("")
// Demonstrate schema evolution
demonstrateMigration()
Console.print("")
// Run tests with swapped handlers
runTestScenario()
Console.print("")
Console.print("╔═══════════════════════════════════════════════════════════╗")
Console.print("║ Key Takeaways: ║")
Console.print("║ ║")
Console.print("║ 1. Effects in signatures = no hidden side effects ║")
Console.print("║ 2. Behavioral types = compile-time guarantees ║")
Console.print("║ 3. Handler swapping = easy testing without mocks ║")
Console.print("║ 4. Schema evolution = safe data migrations ║")
Console.print("╚═══════════════════════════════════════════════════════════╝")
}
// Run the showcase
let _ = run main() with {}