The `lux test` command used Parser::parse_source() and check_program() directly, which meant test files with `import` statements would fail with type errors. Now uses ModuleLoader and check_program_with_modules() to properly resolve imports, and run_with_modules() for execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lux
A functional programming language with first-class effects, schema evolution, and behavioral types.
Philosophy
Make the important things visible.
Most languages hide what matters most: what code can do (effects), how data changes over time (schema evolution), and what guarantees functions provide (behavioral properties). Lux makes all three first-class, compiler-checked language features.
| Principle | What it means |
|---|---|
| Explicit over implicit | Effects in types — see what code does |
| Composition over configuration | No DI frameworks — effects compose naturally |
| Safety without ceremony | Type inference + explicit signatures where they matter |
| Practical over academic | Familiar syntax, ML semantics, no monads |
| One right way | Opinionated formatter, integrated tooling, built-in test framework |
| Tools are the language | lux fmt/lint/check/test/compile — one binary, not seven tools |
See docs/PHILOSOPHY.md for the full philosophy with language comparisons and design rationale.
Core Principles
1. Effects Are Explicit and Composable
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
type User @v1 {
name: String,
email: String
}
type User @v2 {
name: String,
email: String,
age: Option<Int> -- optional field: auto-compatible
}
type User @v3 {
fullName: String, -- renamed: requires migration
email: String,
age: Option<Int>,
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
fn retry<F, T>(action: F): Result<T, Error>
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<T: Ord>(list: List<T>): List<T>
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
-- 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 — Development roadmap and feature status
- 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)
# 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+:
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 effectsfactorial.lux— Recursive functionseffects.lux— Custom effects and handlersdatatypes.lux— ADTs and pattern matchingfunctional.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