# Lux A functional programming language with first-class effects, schema evolution, and behavioral types. ## Vision Most programming languages treat three critical concerns as afterthoughts: 1. **Effects** — What can this code do? (Hidden, untraceable, untestable) 2. **Data Evolution** — Types change, data persists. (Manual migrations, runtime failures) 3. **Behavioral Properties** — Is this idempotent? Does it terminate? (Comments and hope) Lux makes these first-class language features. The compiler knows what your code does, how your data evolves, and what properties your functions guarantee. ## Core Principles ### 1. Effects Are Explicit and Composable ```lux fn fetchUser(id: UserId): User with {Database, Http} = let profile = Http.get("/users/{id}") let prefs = Database.query(userPrefsQuery(id)) User.merge(profile, prefs) -- Testing: swap real effects for mocks test "fetchUser returns merged data" = run fetchUser(testId) with { Database = mockDb({ testId: testPrefs }), Http = mockHttp({ "/users/{testId}": testProfile }) } |> Assert.eq(expectedUser) ``` No hidden side effects. No dependency injection boilerplate. Effects are declared, handlers are swappable, composition just works. ### 2. Schema Evolution Is Built-In ```lux type User @v1 { name: String, email: String } type User @v2 { name: String, email: String, age: Option -- optional field: auto-compatible } type User @v3 { fullName: String, -- renamed: requires migration email: String, age: Option, from @v2 = { fullName: v2.name, ..v2 } } ``` The compiler tracks compatibility. Breaking changes are compile errors. Migrations are code, not config. ### 3. Behavioral Types Are First-Class ```lux fn retry(action: F): Result where F: fn() -> T with {Fail}, where F is idempotent -- enforced! = match action() { Ok(v) => Ok(v), Err(_) => action() -- safe: we know it's idempotent } fn sort(list: List): List is pure, is total, where result.len == list.len, where result.isSorted ``` Properties like `pure`, `total`, `idempotent`, `commutative` are part of the type system. The compiler proves what it can, tests what it can't. ## Example ```lux -- Define an effect effect Logger { fn log(level: Level, msg: String): Unit } -- Define a versioned type type Config @v1 { host: String, port: Int } type Config @v2 { host: String, port: Int, timeout: Duration, from @v1 = { timeout: Duration.seconds(30), ..v1 } } -- A function with explicit effects and properties fn loadConfig(path: Path): Config @v2 with {FileSystem, Logger} is total = Logger.log(Info, "Loading config from {path}") let raw = FileSystem.read(path) Config.parse(raw) -- Run with handlers fn main(): Unit with {Console} = let config = run loadConfig("./config.json") with { FileSystem = realFs, Logger = consoleLogger } Console.print("Loaded: {config}") ``` ## Status **Core Language:** Complete - Full type system with Hindley-Milner inference - Pattern matching with exhaustiveness checking - Algebraic data types, generics, string interpolation - Effect system with handlers - Behavioral types (pure, total, idempotent, deterministic, commutative) - Schema evolution with version tracking **Compilation Targets:** - Interpreter (full-featured) - C backend (functions, closures, pattern matching, lists, reference counting) - JavaScript backend (full language, browser & Node.js, DOM, TEA runtime) **Tooling:** - REPL with history - LSP server (diagnostics, hover, completions, go-to-definition) - Formatter (`lux fmt`) - Package manager (`lux pkg`) - Watch mode / hot reload **Standard Library:** - String, List, Option, Result, Math, JSON modules - Console, File, Http, Random, Time, Process effects - SQL effect (SQLite with transactions) - PostgreSQL effect (connection pooling ready) - DOM effect (40+ browser operations) See: - [docs/ROADMAP.md](./docs/ROADMAP.md) — Development roadmap and feature status - [docs/OVERVIEW.md](./docs/OVERVIEW.md) — Use cases, pros/cons, complexity analysis ## Design Goals | Goal | Approach | |------|----------| | **Correctness by default** | Effects, schemas, and behaviors are compiler-checked | | **Incremental adoption** | Start simple, add properties/versions as needed | | **Zero-cost abstractions** | Effect handlers inline, versions compile away | | **Practical, not academic** | Familiar syntax, clear errors, gradual verification | ## Non-Goals - Not a systems language (no manual memory management) - Not a scripting language (static types required) - Not a proof assistant (verification is practical, not total) ## Building ### With Nix (recommended) ```bash # Build nix build # Run the REPL nix run # Enter development shell nix develop # Run tests nix develop --command cargo test ``` ### With Cargo Requires Rust 1.70+: ```bash cargo build --release ./target/release/lux # REPL ./target/release/lux file.lux # Run a file cargo test # Tests ``` ## Examples See the `examples/` directory: - `hello.lux` — Hello World with effects - `factorial.lux` — Recursive functions - `effects.lux` — Custom effects and handlers - `datatypes.lux` — ADTs and pattern matching - `functional.lux` — Higher-order functions and pipes ### Quick REPL Session ``` $ cargo run Lux v0.1.0 Type :help for help, :quit to exit lux> let x = 42 lux> x * 2 84 lux> fn double(n: Int): Int = n * 2 lux> double(21) 42 lux> [1, 2, 3] |> List.reverse [3, 2, 1] lux> List.map([1, 2, 3], double) [2, 4, 6] lux> String.split("a,b,c", ",") ["a", "b", "c"] lux> Some(42) |> Option.map(double) Some(84) lux> :quit ``` ## Contributing This project is in early design. Contributions welcome in: - Language design discussions (open an issue) - Syntax bikeshedding - Semantic formalization - Compiler implementation (once design stabilizes) ## License MIT