Syntax Changes
These are mostly working notes/records. These changes will be reflected in the docs etc soon. All subject to change.
The semantics of WEFT have always been simple: everything is a signal, ℝⁿ → ℝ. Coordinates are signals. Functions are signals with explicit coordinate parameters. One base type, everything composes.
The syntax didn’t reflect that:
spindleandreturn.Nimplied functions were a different kind of thing.me.ximplied coordinates were a special bundle the runtime hands you, not just values like any other.- the
~operator implied remapping was a distinct operation from calling. - pattern syntax (
->{}) implied data flowing forward rather than signals being pulled.
So I’m rethinking the syntax (also, I found it easy to read but difficult to write).
Definitions
spindle implied that functions are a special category: something you declare, give a body, and explicitly return from. But a function in WEFT is just a signal with named coordinate parameters.
The new form is the same for everything:
brightness = 0.5 -- a signal
double(x) = x * 2 -- a function
spindle and return are both gone. Width is inferred from the right-hand side, or can be asserted explicitly as a check:
img[3] = camera(@x, @y)
A constant, a function, a multi-output signal — all just name[<size>] = expr.
where
where is syntactic substitution, not scoping. Every name in a where block gets replaced with its expression everywhere it appears, including through nested where blocks.
display = (r(@x + shift, @y), g, b(@x - shift, @y))
where {
(r, g, b) = camera(@x,@y);
shift = sin(@t) * 0.05;
}
Order within the block doesn’t matter — these are definitions, not steps.
Because where is substitution, it can override any signal used to define a signal, including @ coords. That’s how you remap signals between coordinate spaces:
flipped = img where { @x = 1 - @x }
warped = img where { @x = @x + sin(@y * 10) * 0.05 }
imgAsSound = img
where {
@x = @t % rowWidth / @w;
@y = floor(@t % rowWidth / @w * @h) / @h;
}
If you know upfront you’ll be parameterizing, the explicit function form is still the right choice. But where means you’re never stuck.
Runtime coordinates
me.x, me.y, me.t become @x, @y, @t. The @ sigil marks them as runtime-provided — the coordinates that define the evaluation space of a backend. Otherwise they’re identical in status to any signal. me. implied a special bundle; @ just means “the runtime provides this.”
User-defined coordinate spaces work the same way:
r = sqrt(@x^2 + @y^2)
theta = atan2(@y, @x)
r and theta are signals just like @x. You can remap into them, pass them to functions, substitute them with where.
Tuples, not bundles
No more bundle[r, g, b] = ... declaration syntax. Tuples are parenthesized comma-separated expressions:
(r, g, b)
Tuples can be destructuring at assignment:
(r, g, b) = camera(@x, @y)
Named strand access (.r, .g, .b) is gone, so names only exist through destructuring. Positional access (.0, .1, .-1) stays (for now).
Before/After
As an example, take a chromatic aberration effect — shifting the red and blue channels in opposite directions, animated over time.
In the old syntax, this looked like:
img[r,g,b] = load_img("foo.png")
shift[val] = sin(me.t) * 0.05
display[r,g,b] = img[r,g,b] -> {
.r(me.x ~ me.x + shift.val, me.y ~ me.y),
.g,
.b(me.x ~ me.x - shift.val, me.y ~ me.y)
}
The image has to be declared upfront with named strands, the shift is a separate named bundle, the remapping syntax (~) appears inline in a pattern block, once per coordinate per channel. The result reads as a series of transforms rather than a description of what you want.
In the new syntax, this would look like:
display = (r(@x + shift, @y), g, b(@x - shift, @y))
where {
(r, g, b) = load("foo.png");
shift = sin(@t) * 0.05;
}
The result comes first, the where block explains what each piece is, r, g, b are destructured from the image inline, shift is defined once and used twice. In my opinion, much easier to read and reason about.
Summary
There are still lots of open questions for me on the new syntax and nothing is set in stone. But so far, what I’m thinking is:
| Old | Replaced by |
|---|---|
spindle keyword | name(params) = expr |
return.N = | tuple expression, width inferred |
me.x, me.y, me.t | @x, @y, @t |
~ remap operator | call syntax + where substitution |
->{} pattern blocks | where + destructuring |
bundle[names] = | (a, b, c) = expr |
Named strand access .r | destructuring |
[a,b].(cond) conditionals | if-then-else |