Primitives
Signals in WEFT are built from two pieces: strands and bundles. A strand is the simplest thing in the language: a function that takes coordinates and returns a single number. One number isn’t enough for most things though. A color needs three (red, green, blue). Stereo audio needs two (left, right). Bundles group strands together so you can work with them as a unit.
Strands
A strand is a function from coordinates to a single number. You define a strand by writing an expression, and that expression gets evaluated at every coordinate WEFT asks about.
brightness.val = me.x + me.y
This says: at any position, brightness.val is the sum of the x and y coordinates (don’t worry about x and y yet, we’ll get to it later). At position (0.2, 0.3), the value is 0.5. At (0.8, 0.1), it’s 0.9. The strand doesn’t store any values—it describes a relationship, and WEFT evaluates it wherever it needs to.
Plain numbers are strands too. When you write 0.5, WEFT treats it as a strand that returns 0.5 at every coordinate. This means you can use numbers anywhere you use strands, and you can mix them freely:
dimmed.val = img.r * 0.5
Here img.r is a strand and 0.5 is a constant strand. The result is a new strand that’s half the red channel everywhere. Because strands define relationships rather than stored values, everything stays in sync automatically. dimmed.val is always half of img.r. If img.r changes, dimmed.val changes with it. There’s no update step. You describe the structure, and WEFT maintains it.
Bundles
A bundle groups strands together under a name. When you declare a bundle, the left side names the bundle and its strands, and the right side provides the definitions:
img[r, g, b] = [1, 0.5, 0.2]
This creates a bundle called img containing three strands. The names on the left match up with the expressions on the right in order, so r is 1 everywhere, g is 0.5, and b is 0.2. Having names for your strands makes code easier to read—img.r is clearer than img.0 when you’re thinking about colors.
When a bundle only has one strand, you can use a shorter syntax. Instead of writing brightness[val] = [me.x + me.y], you can just write brightness.val = me.x + me.y. It means the same thing with less ceremony.
Sometimes you don’t need names at all. The bracket syntax creates anonymous bundles:
img = [0.5, 0.3, 0.8]
You can only access these strands by index, but that’s fine when you’re doing something quick inline—passing a color to a function, say, or building up an intermediate result that you’ll immediately discard.
Finally, bundles can’t contain other bundles. There’s no nesting, no [[a, b], [c, d]]. Bundles hold strands, strands produce numbers, and that’s as deep as it goes. The simplicity of types is what makes WEFT flexible.
Accessing Strands
All computation in WEFT happens at the strand level. Bundles are for organization, but when you actually want to do something—math, combining, passing to a function—you’re working with strands. The . operator pulls a strand out of a bundle.
If you’ve named your strands, you can access them by name:
img[r, g, b] = [1, 0.5, 0.2]
img.r // the red channel
You can also access by index, which is useful when you don’t care about names or when working with anonymous bundles:
img.0 // first strand (same as img.r above)
img.1 // second strand
colors = [0.8, 0.2, 0.4]
colors.0 // first strand
colors.2 // third strand
Negative indices count from the end. img.-1 is the last strand, img.-2 is second to last. This is useful when you don’t know a bundle’s width or just want to grab the end without counting.
Indices can also be expressions. palette.(1 + 1) evaluates to palette.2. More usefully, palette.(some_strand) lets another strand’s value choose which strand to select—this is how WEFT handles conditionals, which we’ll cover later.
Accessing a strand that doesn’t exist is an error. If a bundle has three strands, bundle.3 or bundle.-4 will fail.