Choosing without Branching
WEFT has no if statement. No else, no switch, no ternary operations. This might seem like a limitation, but it falls out naturally from how bundles and indexing already work.
Indexing as selection
You’ve already seen that strand access can use expressions: palette.(1 + 1) is the same as palette.2. The index is just a number, and numbers can come from anywhere.
This gives you conditionals for free. Put your options in a bundle, and use a comparison to pick between them:
[dark, bright].(me.x > 0.5)
If me.x > 0.5 is true, the comparison returns 0 and you get the first strand. If false, it returns 1 and you get the second. Branching is just indexing.
The order might feel backwards. Comparisons return 0 for true and 1 for false because you’re selecting an index, not evaluating a boolean. Think of it as: the first option is what you get when the condition holds.
Multiple options
This pattern extends to more than two choices. If your selector expression returns 0, 1, 2, or 3, you can index into a four-strand bundle:
color.val = [red, green, blue, white].(quadrant.val)
Whatever integer quadrant.val evaluates to, that’s the strand you get.
For continuous values, use floor to snap to integers:
band.val = floor(me.x * 4)
color.val = [red, orange, yellow, green].(band.val)
This divides the canvas into four vertical bands, each a different color.
Pull-Based Evaluation
To understand why this works so well, you need to know how WEFT actually runs your code.
WEFT is pull-based. It starts from the outputs— display, play—and works backwards. When rendering a pixel, WEFT asks: what’s the value of display.r at this coordinate? That strand might depend on another strand, which depends on another, and so on. Each strand gets evaluated only when something pulls from it.
This means strands that nothing depends on never run. Define a strand but never use it? It’s never evaluated. Define a strand inside a bundle that’s never indexed? Same thing—it exists as a function, but nothing ever calls it.
Both Branches Are Defined
This is what makes conditional indexing efficient. When you write [a, b].(condition), both a and b are defined as strands. But only the selected one gets pulled.
If the condition returns 0 at some coordinate, only a gets evaluated there. The strand b exists—it’s a valid function—but nothing samples it, so its computation never runs.
This is different from imperative if/else in a subtle way. Both branches are defined, but only the selected one is evaluated. You’re not choosing which code to run—you’re choosing which strand to pull from.
Recursion
This flexiblity is what makes recursion possible. A recursive strand definition doesn’t immediately evaluate itself—it just defines a function. The recursion only happens if something actually pulls from the recursive case.
spindle fib(n) {
base.val = [0, 1].(n)
recurse.val = fib(n - 1).val + fib(n - 2).val
return.0 = [base.val, recurse.val].(n < 2)
}
When n < 2, the comparison returns 0 and base.val is selected—the recursion is never sampled. When n >= 2, the comparison returns 1 and recurse.val gets pulled, which triggers the recursive calls. Both strands are defined, but only one gets evaluated at each step.