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 {}
|
||||||
22
examples/standard/factorial.lux
Normal file
22
examples/standard/factorial.lux
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Factorial - compute n!
|
||||||
|
|
||||||
|
// Recursive version
|
||||||
|
fn factorial(n: Int): Int =
|
||||||
|
if n <= 1 then 1
|
||||||
|
else n * factorial(n - 1)
|
||||||
|
|
||||||
|
// Tail-recursive version (optimized)
|
||||||
|
fn factorialTail(n: Int, acc: Int): Int =
|
||||||
|
if n <= 1 then acc
|
||||||
|
else factorialTail(n - 1, n * acc)
|
||||||
|
|
||||||
|
fn factorial2(n: Int): Int = factorialTail(n, 1)
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} = {
|
||||||
|
Console.print("Factorial examples:")
|
||||||
|
Console.print("5! = " + toString(factorial(5)))
|
||||||
|
Console.print("10! = " + toString(factorial(10)))
|
||||||
|
Console.print("20! = " + toString(factorial2(20)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
22
examples/standard/fizzbuzz.lux
Normal file
22
examples/standard/fizzbuzz.lux
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// FizzBuzz - print numbers 1-100, but:
|
||||||
|
// - multiples of 3: print "Fizz"
|
||||||
|
// - multiples of 5: print "Buzz"
|
||||||
|
// - multiples of both: print "FizzBuzz"
|
||||||
|
|
||||||
|
fn fizzbuzz(n: Int): String =
|
||||||
|
if n % 15 == 0 then "FizzBuzz"
|
||||||
|
else if n % 3 == 0 then "Fizz"
|
||||||
|
else if n % 5 == 0 then "Buzz"
|
||||||
|
else toString(n)
|
||||||
|
|
||||||
|
fn printFizzbuzz(i: Int, max: Int): Unit with {Console} =
|
||||||
|
if i > max then ()
|
||||||
|
else {
|
||||||
|
Console.print(fizzbuzz(i))
|
||||||
|
printFizzbuzz(i + 1, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
printFizzbuzz(1, 100)
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
25
examples/standard/guessing_game.lux
Normal file
25
examples/standard/guessing_game.lux
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Number guessing game - demonstrates Random and Console effects
|
||||||
|
|
||||||
|
fn gameLoop(secret: Int, attempts: Int): Unit with {Console} = {
|
||||||
|
Console.print("Guess the number (1-100), attempt " + toString(attempts) + ":")
|
||||||
|
let guess = Console.readInt()
|
||||||
|
if guess == secret then
|
||||||
|
Console.print("Correct! You got it in " + toString(attempts) + " attempts!")
|
||||||
|
else if guess < secret then {
|
||||||
|
Console.print("Too low!")
|
||||||
|
gameLoop(secret, attempts + 1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Console.print("Too high!")
|
||||||
|
gameLoop(secret, attempts + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, Random} = {
|
||||||
|
Console.print("Welcome to the Guessing Game!")
|
||||||
|
Console.print("I'm thinking of a number between 1 and 100...")
|
||||||
|
let secret = Random.int(1, 100)
|
||||||
|
gameLoop(secret, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
7
examples/standard/hello_world.lux
Normal file
7
examples/standard/hello_world.lux
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// The classic first program
|
||||||
|
// Expected output: Hello, World!
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} =
|
||||||
|
Console.print("Hello, World!")
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
29
examples/standard/primes.lux
Normal file
29
examples/standard/primes.lux
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Prime number utilities
|
||||||
|
|
||||||
|
fn isPrime(n: Int): Bool =
|
||||||
|
if n < 2 then false
|
||||||
|
else isPrimeHelper(n, 2)
|
||||||
|
|
||||||
|
fn isPrimeHelper(n: Int, i: Int): Bool =
|
||||||
|
if i * i > n then true
|
||||||
|
else if n % i == 0 then false
|
||||||
|
else isPrimeHelper(n, i + 1)
|
||||||
|
|
||||||
|
// Find first n primes
|
||||||
|
fn findPrimes(count: Int): Unit with {Console} =
|
||||||
|
findPrimesHelper(2, count)
|
||||||
|
|
||||||
|
fn findPrimesHelper(current: Int, remaining: Int): Unit with {Console} =
|
||||||
|
if remaining <= 0 then ()
|
||||||
|
else if isPrime(current) then {
|
||||||
|
Console.print(toString(current))
|
||||||
|
findPrimesHelper(current + 1, remaining - 1)
|
||||||
|
}
|
||||||
|
else findPrimesHelper(current + 1, remaining)
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} = {
|
||||||
|
Console.print("First 20 prime numbers:")
|
||||||
|
findPrimes(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
43
examples/standard/stdlib_demo.lux
Normal file
43
examples/standard/stdlib_demo.lux
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Standard Library Demo
|
||||||
|
// Demonstrates the built-in modules: List, String, Option, Math
|
||||||
|
|
||||||
|
fn main(): Unit with {Console} = {
|
||||||
|
Console.print("=== List Operations ===")
|
||||||
|
let nums = [1, 2, 3, 4, 5]
|
||||||
|
Console.print("Original: " + toString(nums))
|
||||||
|
Console.print("Mapped (*2): " + toString(List.map(nums, fn(x: Int): Int => x * 2)))
|
||||||
|
Console.print("Filtered (even): " + toString(List.filter(nums, fn(x: Int): Bool => x % 2 == 0)))
|
||||||
|
Console.print("Sum (fold): " + toString(List.fold(nums, 0, fn(acc: Int, x: Int): Int => acc + x)))
|
||||||
|
Console.print("Length: " + toString(List.length(nums)))
|
||||||
|
Console.print("Reversed: " + toString(List.reverse(nums)))
|
||||||
|
Console.print("Range 1-5: " + toString(List.range(1, 6)))
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("=== String Operations ===")
|
||||||
|
let text = " Hello, World! "
|
||||||
|
Console.print("Original: \"" + text + "\"")
|
||||||
|
Console.print("Trimmed: \"" + String.trim(text) + "\"")
|
||||||
|
Console.print("Upper: " + String.toUpper(text))
|
||||||
|
Console.print("Lower: " + String.toLower(text))
|
||||||
|
Console.print("Contains 'World': " + toString(String.contains(text, "World")))
|
||||||
|
Console.print("Split by comma: " + toString(String.split("a,b,c", ",")))
|
||||||
|
Console.print("Join with dash: " + String.join(["x", "y", "z"], "-"))
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("=== Option Operations ===")
|
||||||
|
let some_val = Some(42)
|
||||||
|
let none_val: Option<Int> = None
|
||||||
|
Console.print("Some(42) mapped (*2): " + toString(Option.map(some_val, fn(x: Int): Int => x * 2)))
|
||||||
|
Console.print("None mapped: " + toString(Option.map(none_val, fn(x: Int): Int => x * 2)))
|
||||||
|
Console.print("Some(42) getOrElse(0): " + toString(Option.getOrElse(some_val, 0)))
|
||||||
|
Console.print("None getOrElse(0): " + toString(Option.getOrElse(none_val, 0)))
|
||||||
|
|
||||||
|
Console.print("")
|
||||||
|
Console.print("=== Math Operations ===")
|
||||||
|
Console.print("abs(-5): " + toString(Math.abs(-5)))
|
||||||
|
Console.print("min(3, 7): " + toString(Math.min(3, 7)))
|
||||||
|
Console.print("max(3, 7): " + toString(Math.max(3, 7)))
|
||||||
|
Console.print("pow(2, 10): " + toString(Math.pow(2, 10)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = run main() with {}
|
||||||
Reference in New Issue
Block a user