Skip to content

A tour of Vex

Each lesson adds one call to the chain of the lesson before it. Each example runs the real @mark1russell7/vex in your browser.

The examples read four boxes: A, B, C and D. Each box has a position, a size, a weight, a color and a name. Drag a box in any example: each example on the page evaluates again with the new position.

The root of each chain comes from these lines:

import { space, vex } from "@mark1russell7/vex";
import { BoolDomain, ColorDomain, NumDomain, Vec2Domain } from "@mark1russell7/vex-domains";
const root = vex(Vec2Domain, NumDomain, BoolDomain, ColorDomain).over(space.record(boxes));

from("position") reads the field position of the record at the focus. At the start, the focus is the origin: the key where the evaluation starts. Select another origin to read the position of another box.

1. Read a field at the focusorigin
root.from("position")
step 1 of 1

Result at A: (2, 2)

originABCD
result(2, 2)(6, 5)(13, 3)(4, 11)

._ lists the ops of the domain of the current value. A bare string is a field at the focus, so add("size") adds the size of the same box. The result is the far corner of the box.

2. Apply an oporigin
root.from("position")._.add("size")
step 3 of 3

Result at A: (7, 6)

originABCD
result(7, 6)(10, 9)(16, 9)(11, 13)

root.of("B", "position") reads a field at the key B, from each origin. It is like an absolute reference in a spreadsheet. At the origin B, the result is the size of B.

3. Read at another keyorigin
root.from("position")._.add("size")
  ._.subtract(root.of("B", "position"))
step 5 of 5

Result at A: (1, 1)

originABCD
result(1, 1)(4, 4)(10, 4)(5, 8)

length() gives a number. After it, the chain has no Vec2 ops: the type of the chain follows the value. The Num domain gives the ops of numbers.

4. Change the value kindorigin
root.from("position")._.add("size")
  ._.subtract(root.of("B", "position"))
  ._.length()
step 6 of 6

Result at A: 1.41

originABCD
result1.415.6610.779.43

to("C") moves the focus for the field names after it. The first position reads at the origin, and the second position reads at C. The result is the distance from each box to C.

5. Move the focusorigin
root.from("position").to("C")._.subtract("position")._.length()
step 4 of 4

Result at A: 11.05

originABCD
result11.057.28012.04

others(body) evaluates the body at each key except the focus. The body starts with the current value from the origin, and its field names read at each target. The result is a list with one item for each other box.

6. An axis: each other boxorigin
root.from("position")
  .others((e) => e._.subtract("position")._.length())
  .values()
step 16 of 16

Result at A: [5, 11.05, 9.22]

originABCD
result[5, 11.05, 9.22][5, 7.28, 6.32][11.05, 7.28, 12.04][9.22, 6.32, 12.04]

min() gives the smallest value of the list. The other reductions are max, sum, mean, count, any, all, none, first, values and reduce(op). By default, a reduction skips error items. With { strict: true }, the first error item is the result.

7. Reduce the listorigin
root.from("position")
  .others((e) => e._.subtract("position")._.length())
  .min()
step 16 of 16

Result at A: 5

originABCD
result557.286.32

Vex does not throw. A division by the vector (0, 0) gives the error #NUM!. ifError gives a fallback when the chain gives an error, like IFERROR in a spreadsheet. Move the slider to see the error, and then the fallback.

8. Catch an error valueorigin
root.from("position")._.divide(0).ifError(root.from("position"))
step 5 of 5

Result at A: (2, 2)

originABCD
result(2, 2)(6, 5)(13, 3)(4, 11)
  • The concepts explain spaces, addresses, axes and domains.
  • The Lab lets you build a chain from chips and change its JSON.
  • Why none? explains each error code.