feat: add comprehensive example programs
Standard examples (examples/standard/): - hello_world: Basic effect usage - fizzbuzz: Classic programming exercise - factorial: Recursive and tail-recursive versions - primes: Prime number generation - guessing_game: Interactive Random + Console effects - stdlib_demo: Demonstrates List, String, Option, Math modules Showcase examples (examples/showcase/): - ask_pattern: Resumable effects for config/environment - custom_logging: Custom effect with handler - early_return: Fail effect for clean error handling - effect_composition: Combining multiple effects - higher_order: Closures and function composition - pattern_matching: ADTs and exhaustive matching Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
39
examples/showcase/ask_pattern.lux
Normal file
39
examples/showcase/ask_pattern.lux
Normal file
@@ -0,0 +1,39 @@
|
||||
// The "Ask" Pattern - Resumable Effects
|
||||
//
|
||||
// Unlike exceptions which unwind the stack, effect handlers can
|
||||
// RESUME with a value. This enables "ask the environment" patterns.
|
||||
//
|
||||
// Expected output:
|
||||
// Need config: api_url
|
||||
// Got: https://api.example.com
|
||||
// Need config: timeout
|
||||
// Got: 30
|
||||
// Configured with url=https://api.example.com, timeout=30
|
||||
|
||||
effect Config {
|
||||
fn get(key: String): String
|
||||
}
|
||||
|
||||
fn configure(): String with {Config, Console} = {
|
||||
Console.print("Need config: api_url")
|
||||
let url = Config.get("api_url")
|
||||
Console.print("Got: " + url)
|
||||
Console.print("Need config: timeout")
|
||||
let timeout = Config.get("timeout")
|
||||
Console.print("Got: " + timeout)
|
||||
"Configured with url=" + url + ", timeout=" + timeout
|
||||
}
|
||||
|
||||
handler envConfig: Config {
|
||||
fn get(key) =
|
||||
if key == "api_url" then resume("https://api.example.com")
|
||||
else if key == "timeout" then resume("30")
|
||||
else resume("unknown")
|
||||
}
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
let result = run configure() with { Config = envConfig }
|
||||
Console.print(result)
|
||||
}
|
||||
|
||||
let output = run main() with {}
|
||||
44
examples/showcase/custom_logging.lux
Normal file
44
examples/showcase/custom_logging.lux
Normal file
@@ -0,0 +1,44 @@
|
||||
// Custom Logging with Effects
|
||||
//
|
||||
// This demonstrates how effects let you abstract side effects.
|
||||
// The same code can be run with different logging implementations.
|
||||
//
|
||||
// Expected output:
|
||||
// [INFO] Starting computation
|
||||
// [DEBUG] x = 10
|
||||
// [INFO] Processing
|
||||
// [DEBUG] result = 20
|
||||
// Final: 20
|
||||
|
||||
effect Log {
|
||||
fn info(msg: String): Unit
|
||||
fn debug(msg: String): Unit
|
||||
}
|
||||
|
||||
fn computation(): Int with {Log} = {
|
||||
Log.info("Starting computation")
|
||||
let x = 10
|
||||
Log.debug("x = " + toString(x))
|
||||
Log.info("Processing")
|
||||
let result = x * 2
|
||||
Log.debug("result = " + toString(result))
|
||||
result
|
||||
}
|
||||
|
||||
handler consoleLogger: Log {
|
||||
fn info(msg) = {
|
||||
Console.print("[INFO] " + msg)
|
||||
resume(())
|
||||
}
|
||||
fn debug(msg) = {
|
||||
Console.print("[DEBUG] " + msg)
|
||||
resume(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
let result = run computation() with { Log = consoleLogger }
|
||||
Console.print("Final: " + toString(result))
|
||||
}
|
||||
|
||||
let output = run main() with {}
|
||||
39
examples/showcase/early_return.lux
Normal file
39
examples/showcase/early_return.lux
Normal file
@@ -0,0 +1,39 @@
|
||||
// Early Return with Fail Effect
|
||||
//
|
||||
// The Fail effect provides clean early termination.
|
||||
// Functions declare their failure modes in the type signature.
|
||||
//
|
||||
// Expected output:
|
||||
// Parsing "42"...
|
||||
// Result: 42
|
||||
// Parsing "100"...
|
||||
// Result: 100
|
||||
// Dividing 100 by 4...
|
||||
// Result: 25
|
||||
|
||||
fn parsePositive(s: String): Int with {Fail, Console} = {
|
||||
Console.print("Parsing \"" + s + "\"...")
|
||||
if s == "42" then 42
|
||||
else if s == "100" then 100
|
||||
else Fail.fail("Invalid number: " + s)
|
||||
}
|
||||
|
||||
fn safeDivide(a: Int, b: Int): Int with {Fail, Console} = {
|
||||
Console.print("Dividing " + toString(a) + " by " + toString(b) + "...")
|
||||
if b == 0 then Fail.fail("Division by zero")
|
||||
else a / b
|
||||
}
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
// These succeed
|
||||
let n1 = run parsePositive("42") with {}
|
||||
Console.print("Result: " + toString(n1))
|
||||
|
||||
let n2 = run parsePositive("100") with {}
|
||||
Console.print("Result: " + toString(n2))
|
||||
|
||||
let n3 = run safeDivide(100, 4) with {}
|
||||
Console.print("Result: " + toString(n3))
|
||||
}
|
||||
|
||||
let output = run main() with {}
|
||||
39
examples/showcase/effect_composition.lux
Normal file
39
examples/showcase/effect_composition.lux
Normal file
@@ -0,0 +1,39 @@
|
||||
// Effect Composition - Combine multiple effects cleanly
|
||||
//
|
||||
// Unlike monad transformers (which have ordering issues),
|
||||
// effects can be freely combined without boilerplate.
|
||||
// Each handler handles its own effect, ignoring others.
|
||||
//
|
||||
// Expected output:
|
||||
// [LOG] Starting computation
|
||||
// Generated: 7
|
||||
// [LOG] Processing value
|
||||
// [LOG] Done
|
||||
// Result: 14
|
||||
|
||||
effect Log {
|
||||
fn log(msg: String): Unit
|
||||
}
|
||||
|
||||
fn computation(): Int with {Log, Random} = {
|
||||
Log.log("Starting computation")
|
||||
let x = Random.int(1, 10)
|
||||
Log.log("Processing value")
|
||||
let result = x * 2
|
||||
Log.log("Done")
|
||||
result
|
||||
}
|
||||
|
||||
handler consoleLog: Log {
|
||||
fn log(msg) = Console.print("[LOG] " + msg)
|
||||
}
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
let result = run computation() with {
|
||||
Log = consoleLog
|
||||
}
|
||||
Console.print("Generated: " + toString(result / 2))
|
||||
Console.print("Result: " + toString(result))
|
||||
}
|
||||
|
||||
let output = run main() with {}
|
||||
40
examples/showcase/higher_order.lux
Normal file
40
examples/showcase/higher_order.lux
Normal file
@@ -0,0 +1,40 @@
|
||||
// Higher-Order Functions and Closures
|
||||
//
|
||||
// Functions are first-class values in Lux.
|
||||
// Closures capture their environment.
|
||||
//
|
||||
// Expected output:
|
||||
// Square of 5: 25
|
||||
// Cube of 3: 27
|
||||
// Add 10 to 5: 15
|
||||
// Add 10 to 20: 30
|
||||
// Composed: 15625 (cube(square(5)) = cube(25) = 15625)
|
||||
|
||||
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
||||
|
||||
fn compose(f: fn(Int): Int, g: fn(Int): Int): fn(Int): Int =
|
||||
fn(x: Int): Int => f(g(x))
|
||||
|
||||
fn square(n: Int): Int = n * n
|
||||
|
||||
fn cube(n: Int): Int = n * n * n
|
||||
|
||||
fn makeAdder(n: Int): fn(Int): Int =
|
||||
fn(x: Int): Int => x + n
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
// Apply functions
|
||||
Console.print("Square of 5: " + toString(apply(square, 5)))
|
||||
Console.print("Cube of 3: " + toString(apply(cube, 3)))
|
||||
|
||||
// Closures
|
||||
let add10 = makeAdder(10)
|
||||
Console.print("Add 10 to 5: " + toString(add10(5)))
|
||||
Console.print("Add 10 to 20: " + toString(add10(20)))
|
||||
|
||||
// Function composition
|
||||
let squareThenCube = compose(cube, square)
|
||||
Console.print("Composed: " + toString(squareThenCube(5)))
|
||||
}
|
||||
|
||||
let output = run main() with {}
|
||||
55
examples/showcase/pattern_matching.lux
Normal file
55
examples/showcase/pattern_matching.lux
Normal file
@@ -0,0 +1,55 @@
|
||||
// Algebraic Data Types and Pattern Matching
|
||||
//
|
||||
// Lux has powerful ADTs with exhaustive pattern matching.
|
||||
// The type system ensures all cases are handled.
|
||||
//
|
||||
// Expected output:
|
||||
// Evaluating: (2 + 3)
|
||||
// Result: 5
|
||||
// Evaluating: ((1 + 2) * (3 + 4))
|
||||
// Result: 21
|
||||
// Evaluating: (10 - (2 * 3))
|
||||
// Result: 4
|
||||
|
||||
type Expr =
|
||||
| Num(Int)
|
||||
| Add(Expr, Expr)
|
||||
| Sub(Expr, Expr)
|
||||
| Mul(Expr, Expr)
|
||||
|
||||
fn eval(e: Expr): Int =
|
||||
match e {
|
||||
Num(n) => n,
|
||||
Add(a, b) => eval(a) + eval(b),
|
||||
Sub(a, b) => eval(a) - eval(b),
|
||||
Mul(a, b) => eval(a) * eval(b)
|
||||
}
|
||||
|
||||
fn showExpr(e: Expr): String =
|
||||
match e {
|
||||
Num(n) => toString(n),
|
||||
Add(a, b) => "(" + showExpr(a) + " + " + showExpr(b) + ")",
|
||||
Sub(a, b) => "(" + showExpr(a) + " - " + showExpr(b) + ")",
|
||||
Mul(a, b) => "(" + showExpr(a) + " * " + showExpr(b) + ")"
|
||||
}
|
||||
|
||||
fn evalAndPrint(e: Expr): Unit with {Console} = {
|
||||
Console.print("Evaluating: " + showExpr(e))
|
||||
Console.print("Result: " + toString(eval(e)))
|
||||
}
|
||||
|
||||
fn main(): Unit with {Console} = {
|
||||
// (2 + 3)
|
||||
let e1 = Add(Num(2), Num(3))
|
||||
evalAndPrint(e1)
|
||||
|
||||
// ((1 + 2) * (3 + 4))
|
||||
let e2 = Mul(Add(Num(1), Num(2)), Add(Num(3), Num(4)))
|
||||
evalAndPrint(e2)
|
||||
|
||||
// (10 - (2 * 3))
|
||||
let e3 = Sub(Num(10), Mul(Num(2), Num(3)))
|
||||
evalAndPrint(e3)
|
||||
}
|
||||
|
||||
let output = run main() with {}
|
||||
Reference in New Issue
Block a user