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

A Tutorial Introduction

Adam programs are called sheets, a term borrowed from spreadsheets. This tutorial introduces the language through live examples.

An Adam sheet declares the relationships among a set of properties called cells. These relationships determine how edits propagate through a user interface or scripting system.

Each source fragment defines the cells and relationships shown in its live inspector and graph. Edit values to explore how they propagate. Applications typically bind a sheet to controls or scripts with human-readable labels rather than cell identifiers.

A first sheet

The first example defines one named sheet, with declarations between braces. Modules may contain multiple definitions and imports. This sheet declares two source cells:

sheet hello {
    source width = 1920;
    source height = 1080;
}

A cell is a named, typed storage location. Adam infers width and height as i32 from their initial values. A source cell holds a value supplied from outside the sheet, like a spreadsheet’s value cell.

A sheet describes a graph of cells and constraints. The graph for hello has two unconnected cells.

Filters

A filter clause attaches a domain constraint to a cell, most commonly a range:

sheet volume {
    source level: i32 = 50 filter 0..=100;
}

0..=100 is an inclusive range. Try writing a value outside [0, 100] into level above and watch it snap back into range.

A filter’s bounds don’t have to be constants: 0..=max references another cell. The filters chapter covers filters in full.

Out Cells

An out declaration is like a spreadsheet’s equation cell. Its value is computed from the provided method.

sheet area_demo {
    source width = 10 filter 0..=100;
    source height = 20 filter 0..=100;

    out area := width * height;
}

The output’s method can read other cells; resolution updates its result when those inputs change. The graph draws the relationship as a circle between cells. Heavy arrows and the output cell’s border mark a forced value. The live inspector disables editing for forced cells.

See the outputs chapter for the full treatment.

Cells and Relationships

A plain cell may be a source or a derived output. Relationships connect cells through alternative methods, each solving for a different cell or set of cells.

This relationship maintains \(a = 2b\):

sheet basic_relationship {
    cell a: f64 filter 0.0..=200.0;
    cell b = 2.0 filter 0.0..=100.0;

    relationship {
        a := b * 2.0;
        b := a / 2.0;
    }
}

Resolution selects exactly one method per active relationship, based on cell strength. Cells written more recently have higher strength. The declaration order determines the cells’ initial strength. Cells declared later have a higher strength.

In the graph, you can see the flow change as you write a or b.

The methods in a relationship must be consistent. If the result of the selected method is used to recalculate the non-selected methods, the result should not change the value of the assigned cells within an error epsilon.

Relationships can be chained together. We can express the relationship a <= b <= c like this:

sheet inequality {
    cell a = 10 filter 0..=100;
    cell b = 20 filter 0..=100;
    cell c = 30 filter 0..=100;

    relationship {
        a := min(a, b);
        b := max(a, b);
    }

    relationship {
        b := min(b, c);
        c := max(b, c);
    }
}

The methods here are self-referencing: each reads a cell it also writes. Such a method must be idempotent, leaving its corrected output unchanged when reapplied. Self-referencing methods and filters preserve the cell’s last-written source value.

Slide a to 100 to pull b and c to 100, then slide a back to 0. b and c return to their prior values.

Relationships may connect any number of cells, with each method solving for one or more; see the relationships chapter.

Conditionals

A conditional groups relationships that are only active under a matching condition. It evaluates a match subject, then activates whichever branch’s literal equals the current match value:

sheet constrain {
    source constrain = false;
    cell a = 5 filter 1..=100;
    cell b = 10 filter 1..=100;

    conditional constrain {
        true => {
            relationship {
                a := b;
                b := a;
            }
        }
    }
}

Only the active branch participates. The _ branch, if present, catches any value not in the named branches list, and must be written last. If no branch is matched, the conditional has no effect.

A conditional relationship may force a cell while preserving its source value. Releasing the relationship restores that source. Host UIs commonly disable controls for forced cells.

sheet conditional_forced {
    source constrain = false;
    cell a = 5 filter 1..=100;

    conditional constrain {
        true => {
            relationship {
                a := 42;
            }
        }
    }
}

Requirements

A filter on a derived cell reports a violation without changing the value:

sheet filter_diagnostic {
    cell width: i32 filter 0..=100;
    cell height = 150 filter 0..=200;

    relationship {
        width := height;
        height := width;
    }
}

A cell may also have one or more requirements. Each requirement is a Boolean diagnostic, not an input gate: a source or writable cell retains a value that violates a requirement, and propagation reports the requirement’s failed validity.

The same rule applies to an out cell. Requirements have an optional name that can be associated with a message explaining the issue.

sheet area_demo {
    source width = 10 filter 1..=100;
    source height = 10 filter 1..=100;

    out area := width * height require {
        @not_too_big area <= 1000;
    };
}

Where to go next

Continue with sheets, cells, and types for declaration and type rules, or use the reference manual to look up grammar, built-in types, and errors.