Write comprehensive PHILOSOPHY.md covering Lux's six core principles (explicit over implicit, composition over configuration, safety without ceremony, practical over academic, one right way, tools are the language) with detailed comparisons against JS/TS, Python, Rust, Go, Java/C#, Haskell/Elm, and Gleam/Elixir. Includes tooling audit and improvement suggestions. Add `lux philosophy` command to the compiler, update help screen with abbreviated philosophy, and link from README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
250 lines
6.4 KiB
Markdown
250 lines
6.4 KiB
Markdown
# 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](./docs/PHILOSOPHY.md) for the full philosophy with language comparisons and design rationale.
|
|
|
|
## 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<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
|
|
|
|
```lux
|
|
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
|
|
|
|
```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
|