Files
lux/docs/guide/04-data-types.md
Brandon Lucas 44f88afcf8 docs: add comprehensive language documentation
Documentation structure inspired by Rust Book, Elm Guide, and others:

Guide (10 chapters):
- Introduction and setup
- Basic types (Int, String, Bool, List, Option, Result)
- Functions (closures, higher-order, composition)
- Data types (ADTs, pattern matching, records)
- Effects (the core innovation)
- Handlers (patterns and techniques)
- Modules (imports, exports, organization)
- Error handling (Fail, Option, Result)
- Standard library reference
- Advanced topics (traits, generics, optimization)

Reference:
- Complete syntax reference

Tutorials:
- Calculator (parsing, evaluation, REPL)
- Dependency injection (testing with effects)
- Project ideas (16 projects by difficulty)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-13 17:43:41 -05:00

6.1 KiB

Chapter 4: Data Types

Lux has algebraic data types (ADTs)—a powerful way to model data with variants and pattern matching.

Defining Types

Enums (Sum Types)

A type that can be one of several variants:

type Color =
    | Red
    | Green
    | Blue

let c: Color = Red

Variants with Data

Variants can carry data:

type Shape =
    | Circle(Int)           // radius
    | Rectangle(Int, Int)   // width, height
    | Point

let s1 = Circle(5)
let s2 = Rectangle(10, 20)
let s3 = Point

Named Fields

For clarity, use record variants:

type Person =
    | Person { name: String, age: Int }

let alice = Person { name: "Alice", age: 30 }

Pattern Matching

The match expression destructures data:

fn colorName(c: Color): String =
    match c {
        Red => "red",
        Green => "green",
        Blue => "blue"
    }

Extracting Data

fn area(s: Shape): Int =
    match s {
        Circle(r) => 3 * r * r,       // Approximate π as 3
        Rectangle(w, h) => w * h,
        Point => 0
    }

area(Circle(5))       // 75
area(Rectangle(4, 5)) // 20

Exhaustiveness

The compiler ensures you handle all cases:

fn colorName(c: Color): String =
    match c {
        Red => "red",
        Green => "green"
        // Error: non-exhaustive pattern, missing Blue
    }

Wildcard Pattern

Use _ to match anything:

fn isRed(c: Color): Bool =
    match c {
        Red => true,
        _ => false       // Matches Green, Blue, anything else
    }

Guards

Add conditions to patterns:

fn classify(n: Int): String =
    match n {
        0 => "zero",
        n if n > 0 => "positive",
        _ => "negative"
    }

Nested Patterns

Match deep structures:

type Expr =
    | Num(Int)
    | Add(Expr, Expr)
    | Mul(Expr, Expr)

fn simplify(e: Expr): Expr =
    match e {
        Add(Num(0), x) => x,          // 0 + x = x
        Add(x, Num(0)) => x,          // x + 0 = x
        Mul(Num(0), _) => Num(0),     // 0 * x = 0
        Mul(_, Num(0)) => Num(0),     // x * 0 = 0
        Mul(Num(1), x) => x,          // 1 * x = x
        Mul(x, Num(1)) => x,          // x * 1 = x
        _ => e                         // No simplification
    }

Built-in ADTs

Option

For optional values:

type Option<T> =
    | Some(T)
    | None

Usage:

fn safeDivide(a: Int, b: Int): Option<Int> =
    if b == 0 then None
    else Some(a / b)

fn showResult(opt: Option<Int>): String =
    match opt {
        Some(n) => "Result: " + toString(n),
        None => "Cannot divide by zero"
    }

showResult(safeDivide(10, 2))  // "Result: 5"
showResult(safeDivide(10, 0))  // "Cannot divide by zero"

Result<T, E>

For operations that can fail with an error:

type Result<T, E> =
    | Ok(T)
    | Err(E)

Usage:

fn parseAge(s: String): Result<Int, String> =
    // Simplified - assume we have a real parser
    if s == "42" then Ok(42)
    else Err("Invalid age: " + s)

fn handleAge(r: Result<Int, String>): String =
    match r {
        Ok(age) => "Age is " + toString(age),
        Err(msg) => "Error: " + msg
    }

List

Lists are built-in but conceptually:

type List<T> =
    | Nil
    | Cons(T, List<T>)

Pattern match on lists:

fn sum(nums: List<Int>): Int =
    match nums {
        [] => 0,
        [x, ...rest] => x + sum(rest)
    }

fn length<T>(list: List<T>): Int =
    match list {
        [] => 0,
        [_, ...rest] => 1 + length(rest)
    }

Recursive Types

Types can reference themselves:

type Tree<T> =
    | Leaf(T)
    | Node(Tree<T>, Tree<T>)

fn sumTree(t: Tree<Int>): Int =
    match t {
        Leaf(n) => n,
        Node(left, right) => sumTree(left) + sumTree(right)
    }

let tree = Node(Node(Leaf(1), Leaf(2)), Leaf(3))
sumTree(tree)  // 6

Type Aliases

Give names to existing types:

type UserId = Int
type Username = String
type UserMap = List<(UserId, Username)>

Records

Anonymous record types:

let point = { x: 10, y: 20 }
point.x  // 10
point.y  // 20

// With type annotation
type Point = { x: Int, y: Int }
let p: Point = { x: 5, y: 10 }

Record Update

Create new records based on existing ones:

let p1 = { x: 10, y: 20 }
let p2 = { ...p1, x: 15 }  // { x: 15, y: 20 }

Practical Example: Expression Evaluator

type Expr =
    | Num(Int)
    | Add(Expr, Expr)
    | Sub(Expr, Expr)
    | Mul(Expr, Expr)
    | Div(Expr, Expr)

fn eval(e: Expr): Result<Int, String> =
    match e {
        Num(n) => Ok(n),
        Add(a, b) => {
            match (eval(a), eval(b)) {
                (Ok(x), Ok(y)) => Ok(x + y),
                (Err(e), _) => Err(e),
                (_, Err(e)) => Err(e)
            }
        },
        Sub(a, b) => {
            match (eval(a), eval(b)) {
                (Ok(x), Ok(y)) => Ok(x - y),
                (Err(e), _) => Err(e),
                (_, Err(e)) => Err(e)
            }
        },
        Mul(a, b) => {
            match (eval(a), eval(b)) {
                (Ok(x), Ok(y)) => Ok(x * y),
                (Err(e), _) => Err(e),
                (_, Err(e)) => Err(e)
            }
        },
        Div(a, b) => {
            match (eval(a), eval(b)) {
                (Ok(_), Ok(0)) => Err("Division by zero"),
                (Ok(x), Ok(y)) => Ok(x / y),
                (Err(e), _) => Err(e),
                (_, Err(e)) => Err(e)
            }
        }
    }

// (10 + 5) * 2
let expr = Mul(Add(Num(10), Num(5)), Num(2))
eval(expr)  // Ok(30)

// 10 / 0
let bad = Div(Num(10), Num(0))
eval(bad)  // Err("Division by zero")

Summary

Concept Syntax Example
Enum type T = | A | B type Bool = | True | False
With data | Variant(Type) | Some(Int)
Match match x { ... } match opt { Some(n) => n, None => 0 }
Wildcard _ _ => "default"
Guard pattern if cond n if n > 0 => "positive"
Record { field: value } { x: 10, y: 20 }

Next

Chapter 5: Effects - The core innovation of Lux.