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>
6.1 KiB
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.