init lux
This commit is contained in:
301
docs/OVERVIEW.md
Normal file
301
docs/OVERVIEW.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Lux Language Overview
|
||||
|
||||
## What is Lux?
|
||||
|
||||
Lux is a statically-typed functional programming language with **algebraic effects** as a first-class feature. It makes side effects explicit, trackable, and testable.
|
||||
|
||||
## What Can You Do With It?
|
||||
|
||||
### Currently Working
|
||||
|
||||
```lux
|
||||
// Functions with type inference
|
||||
fn factorial(n: Int): Int =
|
||||
if n <= 1 then 1 else n * factorial(n - 1)
|
||||
|
||||
// Higher-order functions
|
||||
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
||||
fn double(x: Int): Int = x * 2
|
||||
let result = apply(double, 21) // 42
|
||||
|
||||
// Lambdas and closures
|
||||
let add = fn(a: Int, b: Int): Int => a + b
|
||||
let addFive = fn(x: Int): Int => add(5, x)
|
||||
|
||||
// Pattern matching
|
||||
fn describe(n: Int): String =
|
||||
match n {
|
||||
0 => "zero",
|
||||
1 => "one",
|
||||
_ => "many"
|
||||
}
|
||||
|
||||
// Records
|
||||
let person = { name: "Alice", age: 30 }
|
||||
let age = person.age
|
||||
|
||||
// Tuples
|
||||
let point = (10, 20)
|
||||
|
||||
// Lists
|
||||
let numbers = [1, 2, 3, 4, 5]
|
||||
|
||||
// Pipe operator
|
||||
let result = 5 |> double |> addOne // (5 * 2) + 1 = 11
|
||||
|
||||
// Built-in effects (Console, Fail)
|
||||
Console.print("Hello, world!")
|
||||
|
||||
// Custom effects
|
||||
effect Logger {
|
||||
fn log(level: String, msg: String): Unit
|
||||
}
|
||||
|
||||
// Effect handlers
|
||||
handler consoleLogger: Logger {
|
||||
fn log(level, msg) = Console.print("[" + level + "] " + msg)
|
||||
}
|
||||
|
||||
// Running with handlers
|
||||
fn greet(name: String): Unit with {Logger} =
|
||||
Logger.log("info", "Hello, " + name)
|
||||
|
||||
run greet("Alice") with { Logger = consoleLogger }
|
||||
```
|
||||
|
||||
### Standard Library (Built-in)
|
||||
|
||||
```lux
|
||||
// List operations
|
||||
List.map([1, 2, 3], fn(x: Int): Int => x * 2) // [2, 4, 6]
|
||||
List.filter([1, 2, 3, 4], fn(x: Int): Bool => x > 2) // [3, 4]
|
||||
List.fold([1, 2, 3], 0, fn(acc: Int, x: Int): Int => acc + x) // 6
|
||||
List.head([1, 2, 3]) // Some(1)
|
||||
List.tail([1, 2, 3]) // Some([2, 3])
|
||||
List.concat([1, 2], [3]) // [1, 2, 3]
|
||||
List.reverse([1, 2, 3]) // [3, 2, 1]
|
||||
List.length([1, 2, 3]) // 3
|
||||
List.get([1, 2, 3], 0) // Some(1)
|
||||
List.range(0, 5) // [0, 1, 2, 3, 4]
|
||||
|
||||
// String operations
|
||||
String.split("a,b,c", ",") // ["a", "b", "c"]
|
||||
String.join(["a", "b"], "-") // "a-b"
|
||||
String.trim(" hello ") // "hello"
|
||||
String.contains("hello", "ell") // true
|
||||
String.replace("hi", "i", "ey") // "hey"
|
||||
String.length("hello") // 5
|
||||
String.chars("hi") // ['h', 'i']
|
||||
String.lines("a\nb") // ["a", "b"]
|
||||
|
||||
// Option operations
|
||||
let x = Some(42)
|
||||
let y = None
|
||||
Option.map(x, fn(n: Int): Int => n * 2) // Some(84)
|
||||
Option.flatMap(x, fn(n: Int): Option<Int> => Some(n + 1)) // Some(43)
|
||||
Option.getOrElse(y, 0) // 0
|
||||
Option.isSome(x) // true
|
||||
Option.isNone(y) // true
|
||||
|
||||
// Result operations
|
||||
let ok = Ok(42)
|
||||
let err = Err("failed")
|
||||
Result.map(ok, fn(n: Int): Int => n * 2) // Ok(84)
|
||||
Result.getOrElse(err, 0) // 0
|
||||
Result.isOk(ok) // true
|
||||
Result.isErr(err) // true
|
||||
|
||||
// Utility functions
|
||||
print("Hello") // prints to stdout
|
||||
toString(42) // "42"
|
||||
typeOf([1, 2, 3]) // "List"
|
||||
```
|
||||
|
||||
### Planned (Not Yet Implemented)
|
||||
|
||||
- **Schema Evolution**: Versioned types with automatic migrations
|
||||
- **Behavioral Types**: Properties like `is pure`, `is idempotent`
|
||||
- **Modules/Imports**: Code organization
|
||||
- **Compilation**: Currently interpreter-only
|
||||
|
||||
---
|
||||
|
||||
## Primary Use Cases
|
||||
|
||||
### 1. Learning Effect Systems
|
||||
Lux is an excellent educational tool for understanding algebraic effects without the complexity of Haskell's monad transformers or the academic syntax of languages like Koka.
|
||||
|
||||
### 2. Testable Application Code
|
||||
Effects make dependencies explicit. Swap handlers for testing:
|
||||
|
||||
```lux
|
||||
// Production
|
||||
run app() with { Database = postgres, Http = realHttp }
|
||||
|
||||
// Testing
|
||||
run app() with { Database = mockDb, Http = mockHttp }
|
||||
```
|
||||
|
||||
### 3. Domain Modeling
|
||||
Explicit effects document what code can do:
|
||||
|
||||
```lux
|
||||
fn processOrder(order: Order): Receipt with {Database, Email, Logger}
|
||||
// ^ The signature tells you exactly what side effects this function performs
|
||||
```
|
||||
|
||||
### 4. Prototyping
|
||||
Quick iteration with type inference and a REPL.
|
||||
|
||||
---
|
||||
|
||||
## Pros and Cons
|
||||
|
||||
### Pros
|
||||
|
||||
| Advantage | Description |
|
||||
|-----------|-------------|
|
||||
| **Explicit Effects** | Function signatures show what side effects are possible |
|
||||
| **Testability** | Swap effect handlers for mocking—no dependency injection frameworks |
|
||||
| **Type Safety** | Static types catch errors at compile time |
|
||||
| **Type Inference** | Write less type annotations, compiler figures it out |
|
||||
| **Clean Syntax** | ML-family inspired, minimal boilerplate |
|
||||
| **Pattern Matching** | Destructure data elegantly |
|
||||
| **Immutable by Default** | Easier to reason about |
|
||||
| **REPL** | Interactive development |
|
||||
|
||||
### Cons
|
||||
|
||||
| Limitation | Description |
|
||||
|------------|-------------|
|
||||
| **Interpreter Only** | No compilation to native/JS/WASM yet |
|
||||
| **No Modules** | Can't split code across files |
|
||||
| **Limited IO** | Only Console built-in, no file/network |
|
||||
| **No Generics** | Polymorphic functions not fully implemented |
|
||||
| **New Paradigm** | Effects require learning new concepts |
|
||||
| **Small Ecosystem** | No packages, libraries, or community |
|
||||
| **Early Stage** | Bugs likely, features incomplete |
|
||||
|
||||
---
|
||||
|
||||
## Complexity Assessment
|
||||
|
||||
### Conceptual Complexity
|
||||
|
||||
| Concept | Difficulty | Notes |
|
||||
|---------|------------|-------|
|
||||
| Basic syntax | Easy | Similar to other ML-family languages |
|
||||
| Functions | Easy | Standard functional style |
|
||||
| Pattern matching | Easy | If you know any FP language |
|
||||
| Type system | Medium | Hindley-Milner inference helps |
|
||||
| Effects | Medium | New concept, but simpler than monads |
|
||||
| Handlers | Medium | Requires understanding of continuations |
|
||||
|
||||
### Comparison to Other Languages
|
||||
|
||||
| Language | Complexity | Comparison to Lux |
|
||||
|----------|------------|-------------------|
|
||||
| Python | Simpler | No types, no effect tracking |
|
||||
| TypeScript | Similar | Lux has effects, TS has larger ecosystem |
|
||||
| Elm | Similar | Both pure FP, Lux has general effects |
|
||||
| Haskell | More Complex | Monads harder than algebraic effects |
|
||||
| Koka | Similar | Koka more academic, Lux more practical syntax |
|
||||
| Rust | More Complex | Ownership adds significant complexity |
|
||||
|
||||
### Learning Curve
|
||||
|
||||
**Beginner** (1-2 hours):
|
||||
- Basic expressions, functions, let bindings
|
||||
- If/else, pattern matching
|
||||
- REPL usage
|
||||
|
||||
**Intermediate** (1-2 days):
|
||||
- Custom types and records
|
||||
- Higher-order functions
|
||||
- Built-in effects (Console)
|
||||
|
||||
**Advanced** (1 week):
|
||||
- Custom effect definitions
|
||||
- Effect handlers
|
||||
- Understanding when to use effects vs. regular functions
|
||||
|
||||
---
|
||||
|
||||
## When to Use Lux
|
||||
|
||||
### Good Fit
|
||||
|
||||
- Learning algebraic effects
|
||||
- Prototyping with explicit effect tracking
|
||||
- Small tools where testability matters
|
||||
- Teaching functional programming concepts
|
||||
|
||||
### Not a Good Fit (Yet)
|
||||
|
||||
- Production applications (too early)
|
||||
- Performance-critical code (interpreter)
|
||||
- Large codebases (no modules)
|
||||
- Web development (no JS compilation)
|
||||
- Systems programming (no low-level control)
|
||||
|
||||
---
|
||||
|
||||
## Example Session
|
||||
|
||||
```
|
||||
$ cargo run
|
||||
Lux v0.1.0
|
||||
Type :help for help, :quit to exit
|
||||
|
||||
lux> let x = 42
|
||||
lux> x * 2
|
||||
84
|
||||
lux> fn greet(name: String): Unit with {Console} = Console.print("Hello, " + name)
|
||||
lux> greet("World")
|
||||
Hello, World
|
||||
()
|
||||
lux> let nums = [1, 2, 3]
|
||||
lux> nums
|
||||
[1, 2, 3]
|
||||
lux> :quit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Source Code
|
||||
│
|
||||
▼
|
||||
┌─────────┐
|
||||
│ Lexer │ → Tokens
|
||||
└─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────┐
|
||||
│ Parser │ → AST
|
||||
└─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Type Checker│ → Typed AST + Effect Tracking
|
||||
└─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Interpreter │ → Values + Effect Handling
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Future Roadmap
|
||||
|
||||
1. **Standard Library** - List, String, Option utilities
|
||||
2. **Module System** - Import/export, namespaces
|
||||
3. **JavaScript Backend** - Run in browsers
|
||||
4. **Schema Evolution** - Versioned types
|
||||
5. **Behavioral Types** - is pure, is idempotent
|
||||
6. **LSP Server** - IDE support
|
||||
7. **Package Manager** - Share code
|
||||
253
docs/VISION.md
Normal file
253
docs/VISION.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Lux: Vision and Roadmap
|
||||
|
||||
## The Problems Lux Solves
|
||||
|
||||
### 1. The "What Can This Code Do?" Problem
|
||||
|
||||
In most languages, you can't tell from a function signature what it might do:
|
||||
|
||||
```typescript
|
||||
// TypeScript - what does this do? No idea without reading the code.
|
||||
function processOrder(order: Order): Receipt { ... }
|
||||
```
|
||||
|
||||
Could it hit a database? Send emails? Log? Throw? You don't know until you read every line (and every function it calls).
|
||||
|
||||
**Lux solution:**
|
||||
|
||||
```lux
|
||||
fn processOrder(order: Order): Receipt with {Database, Email, Logger, Fail}
|
||||
```
|
||||
|
||||
The signature *is* the documentation. Code review becomes "should this function really send emails?" Effects are compile-time checked.
|
||||
|
||||
### 2. The Testing Problem
|
||||
|
||||
Testing side-effecting code requires mocking frameworks, dependency injection containers, and boilerplate:
|
||||
|
||||
```typescript
|
||||
// TypeScript - need DI framework, mock libraries, setup/teardown
|
||||
const mockDb = jest.mock('./database');
|
||||
const mockEmail = jest.mock('./email');
|
||||
// ... 50 lines of setup
|
||||
```
|
||||
|
||||
**Lux solution:**
|
||||
|
||||
```lux
|
||||
// Production
|
||||
run processOrder(order) with {
|
||||
Database = postgres(connString),
|
||||
Email = sendgrid(apiKey),
|
||||
Logger = cloudWatch
|
||||
}
|
||||
|
||||
// Test - same code, different handlers
|
||||
run processOrder(order) with {
|
||||
Database = inMemoryDb(testData),
|
||||
Email = collectEmails(sentList), // captures instead of sends
|
||||
Logger = nullLogger
|
||||
}
|
||||
```
|
||||
|
||||
No mocking library. No DI framework. Just swap handlers.
|
||||
|
||||
### 3. The Schema Evolution Problem (Planned)
|
||||
|
||||
Types change. Data persists. Every production system eventually faces:
|
||||
- "I renamed this field, now deserialization breaks"
|
||||
- "I added a required field, old data can't load"
|
||||
- "I need to migrate 10M rows and pray"
|
||||
|
||||
**Lux solution:**
|
||||
|
||||
```lux
|
||||
type User @v1 { name: String, email: String }
|
||||
|
||||
type User @v2 {
|
||||
name: String,
|
||||
email: String,
|
||||
createdAt: Timestamp,
|
||||
from @v1 = { createdAt: Timestamp.epoch(), ..v1 } // migration
|
||||
}
|
||||
|
||||
type User @v3 {
|
||||
fullName: String, // renamed
|
||||
email: String,
|
||||
createdAt: Timestamp,
|
||||
from @v2 = { fullName: v2.name, ..v2 }
|
||||
}
|
||||
|
||||
// Compiler knows: v1 → v2 is auto-compatible, v2 → v3 needs migration
|
||||
// Serialization handles any version automatically
|
||||
```
|
||||
|
||||
### 4. The "Is This Safe?" Problem (Planned)
|
||||
|
||||
Critical properties are documented in comments and hoped for:
|
||||
|
||||
```typescript
|
||||
// IMPORTANT: This function must be idempotent for retry logic!
|
||||
function chargeCard(payment: Payment): Result { ... }
|
||||
```
|
||||
|
||||
**Lux solution:**
|
||||
|
||||
```lux
|
||||
fn chargeCard(payment: Payment): Result
|
||||
is idempotent // Compiler enforces or generates property tests
|
||||
```
|
||||
|
||||
```lux
|
||||
fn retry<F>(action: F, times: Int): Result
|
||||
where F is idempotent // Won't compile if you pass non-idempotent function
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What's Built vs. What's Needed
|
||||
|
||||
### Currently Working (Phase 1: Core Language)
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Lexer/Parser | Done | Full syntax support |
|
||||
| Type Inference | Done | Hindley-Milner |
|
||||
| Functions/Closures | Done | First-class functions |
|
||||
| Pattern Matching | Done | Destructuring, guards |
|
||||
| Records/Tuples/Lists | Done | Basic data structures |
|
||||
| Effect Declarations | Done | `effect Name { ... }` |
|
||||
| Effect Operations | Done | `Effect.operation()` |
|
||||
| Effect Handlers | Done | `handler name: Effect { ... }` |
|
||||
| Run with Handlers | Done | `run expr with { ... }` |
|
||||
| Built-in Console/Fail | Done | Basic IO |
|
||||
| REPL | Done | Interactive development |
|
||||
| Type Checking | Done | With effect tracking |
|
||||
|
||||
### Needed for Real Use (Phase 2: Practical)
|
||||
|
||||
| Feature | Effort | Why It Matters |
|
||||
|---------|--------|----------------|
|
||||
| **Module System** | 2-3 weeks | Can't build real apps without imports |
|
||||
| **Standard Library** | Done | List.map, String.split, Option.map, etc. |
|
||||
| **File/Network Effects** | 1-2 weeks | Real IO beyond Console |
|
||||
| **Better Error Messages** | 2-3 weeks | Elm-quality diagnostics |
|
||||
| **JS/WASM Compilation** | 4-6 weeks | Deploy to browsers/servers |
|
||||
|
||||
### Needed for Full Vision (Phase 3: Differentiation)
|
||||
|
||||
| Feature | Effort | Why It Matters |
|
||||
|---------|--------|----------------|
|
||||
| **Schema Evolution** | 4-6 weeks | The versioned types system |
|
||||
| **Behavioral Types** | 4-6 weeks | is pure, is idempotent, etc. |
|
||||
| **Effect Tracing/Debugging** | 2-3 weeks | Elm-like debugging |
|
||||
| **LSP Server** | 3-4 weeks | IDE support |
|
||||
| **Package Manager** | 2-3 weeks | Share code |
|
||||
|
||||
---
|
||||
|
||||
## Elm-Style Debugging for Effects
|
||||
|
||||
Elm's debugging is famous because:
|
||||
1. **Time-travel**: See app state at any point
|
||||
2. **No runtime crashes**: Everything is Result/Maybe
|
||||
3. **Amazing error messages**: Context, suggestions, examples
|
||||
|
||||
Lux can go further because effects are explicit:
|
||||
|
||||
### Effect Tracing
|
||||
|
||||
Every effect operation can be automatically logged:
|
||||
|
||||
```lux
|
||||
// With tracing enabled:
|
||||
run processOrder(order) with {
|
||||
Database = traced(postgres), // Logs all queries
|
||||
Email = traced(sendgrid), // Logs all sends
|
||||
Logger = traced(cloudWatch) // Meta-logging!
|
||||
}
|
||||
|
||||
// Output:
|
||||
// [00:00:01] Database.query("SELECT * FROM users WHERE id = 42")
|
||||
// [00:00:02] Database.query("SELECT * FROM inventory WHERE sku = 'ABC'")
|
||||
// [00:00:03] Email.send(to: "customer@example.com", subject: "Order Confirmed")
|
||||
// [00:00:03] Logger.log(level: "info", msg: "Order 123 processed")
|
||||
```
|
||||
|
||||
### Effect Replay
|
||||
|
||||
Since all effects are captured, we can replay:
|
||||
|
||||
```lux
|
||||
// Record effects during production
|
||||
let recording = record(processOrder(order)) with { Database = postgres, ... }
|
||||
|
||||
// Replay in development with exact same effect responses
|
||||
replay(recording) with { Database = mockFromRecording(recording) }
|
||||
```
|
||||
|
||||
### State Snapshots
|
||||
|
||||
Since state changes only happen through effects:
|
||||
|
||||
```lux
|
||||
// Snapshot state before/after each effect
|
||||
run debugSession(app) with {
|
||||
State = snapshotted(initialState), // Captures every state change
|
||||
Console = traced(stdout)
|
||||
}
|
||||
|
||||
// Later: inspect state at any point, step forward/backward
|
||||
```
|
||||
|
||||
### Error Messages (To Build)
|
||||
|
||||
Current:
|
||||
```
|
||||
Type error at 15-45: Cannot unify Int with String
|
||||
```
|
||||
|
||||
Goal (Elm-style):
|
||||
```
|
||||
── TYPE MISMATCH ─────────────────────────────────────── src/order.lux
|
||||
|
||||
The `calculateTotal` function expects an `Int` but got a `String`:
|
||||
|
||||
15│ let total = calculateTotal(order.quantity)
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
`order.quantity` is a `String` but `calculateTotal` needs an `Int`.
|
||||
|
||||
Hint: Maybe you need to parse the string?
|
||||
|
||||
let qty = Int.parse(order.quantity)?
|
||||
let total = calculateTotal(qty)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Effort Summary
|
||||
|
||||
**To be minimally useful for real projects:**
|
||||
- Module system + standard library + better errors
|
||||
- **Estimate: 6-8 weeks of focused work**
|
||||
|
||||
**To deliver the full vision (effects + schemas + behavioral types):**
|
||||
- All of the above + schema evolution + behavioral types + compilation
|
||||
- **Estimate: 4-6 months of focused work**
|
||||
|
||||
**To have Elm-quality experience:**
|
||||
- All of the above + debugging tools + LSP + package manager
|
||||
- **Estimate: 8-12 months of focused work**
|
||||
|
||||
---
|
||||
|
||||
## Immediate Next Steps
|
||||
|
||||
1. ~~**Standard Library**~~ - Done! List, String, Option, Result operations
|
||||
2. **Module System** - `import`, `export`, namespaces
|
||||
3. **File Effect** - `FileSystem.read`, `FileSystem.write`
|
||||
4. **Error Message Overhaul** - Source snippets, suggestions, colors
|
||||
5. **JavaScript Backend** - Compile to runnable JS
|
||||
|
||||
These would make Lux usable for small real projects.
|
||||
Reference in New Issue
Block a user