Files
lux/docs/VISION.md
Brandon Lucas 8c7354131e docs: update documentation to match current implementation
- SKILLS.md: Update roadmap phases with actual completion status
  - Phase 0-1 complete, Phase 2-5 partial, resolved design decisions
- OVERVIEW.md: Add HttpServer, Test effect, JIT to completed features
- ROADMAP.md: Add HttpServer, Process, Test effects to done list
- VISION.md: Update Phase 2-3 tables with current status
- guide/05-effects.md: Add Time, HttpServer, Test to effects table
- guide/09-stdlib.md: Add HttpServer, Time, Test effect docs
- reference/syntax.md: Fix interpolation syntax, remove unsupported literals
- testing.md: Add native Test effect documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-14 02:56:42 -05:00

7.6 KiB

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 - 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:

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 - need DI framework, mock libraries, setup/teardown
const mockDb = jest.mock('./database');
const mockEmail = jest.mock('./email');
// ... 50 lines of setup

Lux solution:

// 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:

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:

// IMPORTANT: This function must be idempotent for retry logic!
function chargeCard(payment: Payment): Result { ... }

Lux solution:

fn chargeCard(payment: Payment): Result
    is idempotent  // Compiler enforces or generates property tests
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

Completed (Phase 2: Practical)

Feature Status Notes
Module System Done Imports, exports, aliases, selective imports
Standard Library Done List, String, Option, Result, Math, Json modules
File Effect Done read, write, exists, delete, listDir, mkdir
HTTP Client Effect Done get, post, put, delete
HTTP Server Effect Done listen, accept, respond, stop
Process Effect Done exec, env, args, cwd, exit
Random/Time Effects Done int, float, bool, now, sleep
JIT Compiler Partial Numeric code ~160x faster, strings/ADTs pending
Error Messages Partial Context lines shown, suggestions improving

In Progress (Phase 3: Differentiation)

Feature Status Notes
Schema Evolution ⚠️ Partial Parsing done, type integration pending
Behavioral Types ⚠️ Partial Parsing done, verification pending
LSP Server Done Diagnostics, hover, completions working
Package Manager ⚠️ Partial Manifest parsing exists
Effect Tracing Planned Elm-like debugging

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:

// 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:

// 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:

// 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 - Done! Imports, exports, aliases, selective imports
  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.