// ============================================================================= // 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, // Unix timestamp, optional tags: List, // 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 // ============================================================================= // 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 fn getById(id: String): Option 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 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 = [] fn save(task: Task@latest): Result = { // 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 = { 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 = { Logger.log("Saving task: " + task.id) inner.save(task) } fn getById(id: String): Option = { 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 {}