# 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): 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 = run riskyOperation() with { Fail = catchFail } ``` ### The Choice Pattern Non-determinism: ```lux effect Choice { fn choose(options: List): 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 => 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 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): 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.