Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Algebraic Data Types

Algebraic data types (ADTs) define a type as a fixed set of variants. Each variant may optionally carry data. ADTs enable exhaustive pattern matching — the compiler/runtime can verify that all variants are handled.

Definition

An ADT is defined with the type keyword, listing variants separated by |:

ADTDeftype Identifier = Variant ( | Variant )*

VariantIdentifier ( ( TypeList ) )?

TypeListType ( , Type )*

Unit Variants

Variants without data fields are called unit variants:

type Color = Red | Green | Blue

Unit variants are used as simple enumeration values:

set c to Red
say c  // Red

Data Variants

Variants may carry typed data fields:

type Shape = Circle(Float) | Rect(Float, Float)

Data variants are constructed by calling the variant name as a function:

set circle to Circle(5.0)
set rect to Rect(3.0, 4.0)
say circle  // Circle(5.0)
say rect    // Rect(3.0, 4.0)

Mixed Variants

An ADT may freely mix unit variants and data variants:

type Result = Ok(String) | Err(String) | Pending

Construction

Unit variants are referenced by name alone:

let color = Red

Data variants are constructed by calling the variant name with the appropriate arguments:

let shape = Circle(5.0)
let rect = Rect(3.0, 4.0)

The number and types of arguments must match the variant definition.

Pattern Matching

ADT values are destructured using match expressions. Each arm matches a variant and optionally binds its data fields to variables.

Basic Matching

type Color = Red | Green | Blue

let c = Red

match c {
    Red => say "Red!"
    Green => say "Green!"
    Blue => say "Blue!"
}

Destructuring Data Variants

Data fields are bound to named variables in the match arm:

type Shape = Circle(Float) | Rect(Float, Float)

define describe_shape(s) {
    match s {
        Circle(r) => {
            say "Circle with radius {r}, area = {3.14159 * r * r}"
        }
        Rect(w, h) => {
            say "Rectangle {w}x{h}, area = {w * h}"
        }
    }
}

describe_shape(Circle(5.0))
// Output: Circle with radius 5.0, area = 78.53975

describe_shape(Rect(3.0, 4.0))
// Output: Rectangle 3.0x4.0, area = 12.0

Match as Expression

match can be used as an expression that returns a value:

let area = match shape {
    Circle(r) => 3.14159 * r * r
    Rect(w, h) => w * h
}

Exhaustiveness

A match expression on an ADT should handle all variants. If a variant is missing, the runtime will produce an error when an unhandled variant is encountered.

type Color = Red | Green | Blue

// This handles all variants
match c {
    Red => say "Red!"
    Green => say "Green!"
    Blue => say "Blue!"
}

Wildcard Pattern

The _ pattern matches any value, serving as a catch-all:

match c {
    Red => say "It's red"
    _ => say "It's not red"
}

Built-in ADTs

Forge provides two built-in algebraic types:

These follow the same pattern matching conventions as user-defined ADTs:

let x = Some(42)

match x {
    Some(val) => say "Got: {val}"
    None => say "Nothing"
}

Scope

Variant constructors (e.g., Red, Circle, Some, Ok) are introduced into the scope where the type definition appears. For built-in types like Option and Result, the constructors are globally available.