Files
lux/docs/VISION.md
2026-02-13 02:57:01 -05:00

7.3 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

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:

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