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 chapter is an informal tour of Adam; later chapters go over the same ground in more detail, and the reference manual provides a full specification.

An Adam sheet declares the relationships among a set of properties called cells. These relationships are maintained when edits occur, providing correct behaviors for user interfaces and scripting without having to code complex event handling logic.

Every source fragment below is the complete description used to generate the UI, UI behavior, and graph visualization. The UI is live, so you can change the values and explore the behavior. Within an application, a sheet instance is typically bound to a UI construct or scripting system with human- readable text labels, instead of cell identifiers.

A first sheet

An Adam program is a single sheet, named, with a body of declarations between braces. A simple sheet declares a couple of source cells:

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

A cell is a named, typed storage location: the basic unit of state in a property model. width and height are i32-typed cells, each given an initial value (the types are deduced from the initial value). A source cell is like a spreadsheet’s value cell: it holds a value written into it.

A sheet’s body is a sequence of declarations. A sheet describes a graph of cells and the constraints between them. The graph for hello is just the 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 method on the out cell can reference other cells in the sheet, and the calculation is reapplied when those values change. In the graph representation, the method is a relationship and drawn as a circle between the cells. The heavy arrows and border around the out cell indicate that the value is forced by the relationship. A forced cell’s value is not editable.

See the outputs chapter for the full treatment.

Cells and Relationships

A plain cell declaration acts as a source or out cell. Cells are connected by one or more relationships, each a bundle of methods that satisfy the relationship but solve for a different term (or set of terms).

For example, if we have two values \(a\) and \(b\) where \(a = 2b\), that can be represented as:

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;
    }
}

For any active relationship, exactly one method is selected to execute. The method chosen is based on the strength of the cells. Cells that have been written more recently have a 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);
    }
}

This example also demonstrates two additional features.

  • A method can be self-referential, naming a cell as both a dependency and a result. In such a case, the method must be idempotent.
  • When a cell value is derived in terms of itself via a self-referential method (or filter). The last written value is preserved.

You can see the effect of the second behavior by sliding a to 100 which will pull b and c to 100 and then slide a back to 0. b and c will return to their prior values.

In the relationships chapter, you will see relationships are not limited in their arity (you can have n-way relationships with each method solving for 1 or more cells).

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 value, in this case any source cell value is preserved and restored when the conditional is removed. The UI for a forced cell value will typically disable the control.

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

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

Requirements

Filters on source cells were introduced in the above [[Filters]] section. For an out cell or a cell in an out role, violating a filter will trigger a diagnostic.

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. For source cell or when a cell is writable, the requirements act are a boolean expression that act as a filter rejecting any input that doesn’t satisfy the requirements.

For an out cell, violating a requirement will trigger a diagnostic. 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

That’s the whole language. The remaining chapters, starting with sheets, cells, and types, cover each construct in the depth this chapter skipped past, and the reference manual gives you the full grammar and every built-in type in one place.