# 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 | | `Time` | `now`, `sleep` | Time operations | | `File` | `read`, `write`, `exists`, `delete`, `list`, `mkdir` | File system | | `Process` | `exec`, `env`, `args`, `cwd`, `exit` | System processes | | `Http` | `get`, `post`, `put`, `delete` | HTTP client | | `HttpServer` | `listen`, `accept`, `respond`, `stop` | HTTP server | | `Sql` | `open`, `openMemory`, `close`, `execute`, `query`, `queryOne`, `beginTx`, `commit`, `rollback` | SQLite database | | `Postgres` | `connect`, `close`, `execute`, `query`, `queryOne` | PostgreSQL database | | `Concurrent` | `spawn`, `await`, `yield`, `sleep`, `cancel`, `isRunning`, `taskCount` | Concurrent tasks | | `Channel` | `create`, `send`, `receive`, `tryReceive`, `close` | Inter-task communication | | `Test` | `assert`, `assertEqual`, `assertTrue`, `assertFalse` | Testing | 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): 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 fn execute(sql: String): Int } fn getUsers(): List 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.