Vex is a spreadsheet
A spreadsheet formula reads cells relative to its own cell, and one formula fills a column. A Vex program does the same thing over records. In the sheet below, each box is a row, and each program is a computed column.
Select a computed cell. The bar above the table shows the formula, and the fields that the evaluation read become blue. A failed read becomes red. This is the “Trace Precedents” command of a spreadsheet.
| key | position | size | weight | name | far-corner | gap-to-b | nearest |
|---|---|---|---|---|---|---|---|
| A | (2, 2) | (5, 4) | 2 | "anchor" | |||
| B | (6, 5) | (4, 4) | 3 | "bolt" | |||
| C | (13, 3) | (3, 6) | 5 | "crate" | |||
| D | (4, 11) | (7, 2) | 1 | "deck" |
Blue cells: the fields that the selected cell read. A field name reads at the row of the cell. The axis others reads the same field at each other row.
The map
Section titled “The map”| Spreadsheet | Vex |
|---|---|
| a row | the record at one key of the space |
| a column of data | a field of the records |
| a formula | a program: one expression |
| fill down | all(): the program at each key |
a relative reference, for example B2 |
a bare field name, for example "size" |
an absolute reference, for example $B$2 |
root.of("B", "position") |
an R1C1 offset, for example R[-1]C |
.offset(-1) in an array, .offset(-1, 0) in a grid |
#REF!, #N/A, #VALUE! |
the same codes, as error values |
IFERROR(x, y) |
x.ifError(y) |
| Trace Precedents | the reads in the events of explain(k) |
One formula, filled down
Section titled “One formula, filled down”all() evaluates the chain at each key of the space. This is the start axis. The result is a traversal: one result for each key, with reductions over the results.
const corners = root.from("position")._.add("size").all();
corners.values(); // => [new Vec2(7, 6), new Vec2(10, 9), new Vec2(16, 9), new Vec2(11, 13)]corners.get("B"); // => some(new Vec2(10, 9))root.from("weight").all().sum(); // => some(11)A spreadsheet copies the formula into each cell of the column. Vex keeps one expression and evaluates it at each origin. The spec law AXIS.EXTEND says that the two give the same values. For an expression e that does not read the origin, the item at key k of each(all, e) equals e evaluated at the origin k.
Relative and absolute references
Section titled “Relative and absolute references”A bare field name is a relative reference. It reads at the focus, like B2 in a formula that you copy down a column. root.of("B", "position") is an absolute reference. It reads at the key B from each origin, like $B$2.
root.from("position")._.add("size") // relative: this row ._.subtract(root.of("B", "position")); // absolute: the row BSelect a cell in the column gap-to-b of the sheet. The precedents are the position and the size of that row, and the position of B.
R1C1 offsets in arrays and grids
Section titled “R1C1 offsets in arrays and grids”A record space has names as keys, so it has no “row above”. An array space and a grid space have positions. There, the move offset is the relative reference of the R1C1 style:
const rows = space.array([{ v: 1 }, { v: 2 }, { v: 4 }]);const root = vex(NumDomain).over(rows);
// v minus the v of the row above: R[0]C - R[-1]Cconst delta = root.from("v").offset(-1)._.subtract("v");
delta.all().values(); // => [1, 2]delta.all().errors()[0]?.code; // => "#REF!"offset(-1) moves the focus of the later field names. Thus the first v reads this row, and the second v reads the row above. The first row has no row above, so the result there is #REF!. A spreadsheet gives the same error for a reference above row 1.
In a grid, an offset has two numbers. offset(-1, 0) is the cell above, like R[-1]C[0]. The axis neighbors(8) reads the eight cells around the focus. The Game of Life uses it for its rule.
Error values in cells
Section titled “Error values in cells”A failed cell of a spreadsheet holds an error value, for example #REF! or #N/A. A formula that reads a failed cell also fails. Vex does the same thing: an error goes up the expression tree, and the first error on a path is final.
ifError(fallback) is the Vex form of IFERROR. It gives the value of the chain, or the fallback when the chain gives an error.
root.from("position")._.divide(0) // #NUM! at each key .ifError(root.from("position")); // the position at each keyA reduction over a column skips error items by default. strict() on a traversal, or { strict: true } on an axis reduction, makes the first error the result. Refer to error values for each code.
More than a spreadsheet
Section titled “More than a spreadsheet”- Domain values. A cell holds a number or a text. A Vex field can hold a
Vec2, aColoror a value of your own domain. - Axes. A formula reads a fixed set of cells. An axis reads a relation: each other record, the neighbors of a cell, or the targets that pass a test.
- Types. The typed builder accepts only the fields, keys and ops that exist. Thus many errors cannot occur at run time.
A sheet adds named columns that read each other, like the computed columns of a spreadsheet. A cycle of reads gives #CYCLE!. Refer to sheets and cycles.