Skip to content

mlajtos/fluent

Repository files navigation

Fluent

Reactive programming for differentiable tensors.

∇({ x | Σ(x^2) })([3, 4])   ; [6, 8]

The gradient of a scalar field over a vector: Σ collapses the vector to one number, hands back the slope along each axis. Differentiation over tensors, in one line.

Fluent is a small language and IDE for math you can watch: plots, simulations, models that train. Expressions render, react, and differentiate as you type — on your GPU, in a browser tab.

Open it in the playground — it's the snippet above, live and editable. Every example below opens the same way.

The core ideas

  • Reading order is meaning. No precedence table to memorize — the spacing does the grouping. Space a line out evenly and it runs straight left to right: 1 + 2 * 3 is 9. Close up the multiply and the tighter spacing binds it first: 1 + 2*3 is 7. You read a line the way it runs, and any symbol can become an operator of your own.
  • Gradients come built in. Build a function out of numeric tensor ops — even one you just wrote — and hands you its gradient. It nests, so ∇(∇(f)) is the second derivative.
  • Live by default. Wrap a value in $( ) and it becomes reactive; anything that reads it recomputes when it changes. Drag a slider and only the expressions that depend on it re-run.
  • Everything shows itself. Results draw themselves — numbers and tensors become plots, heatmaps, and images; quoted code becomes a syntax tree. There's no print, you just see the value.

Example gallery

demo
🌀 Mandelbrot — the Mandelbrot set over the whole plane, sharper as you drag the depth ▶ run
🦠 Game of Life — Conway's Game of Life on a large grid ▶ run
🔮 The Name Dreamer — a small transformer that invents new names from a word list ▶ run
🐦 Combinators — combine functions without naming their arguments ▶ run
📷 Live edge detection — your webcam, edge-detected in real time ▶ run
🎵 Live spectrum — your microphone's frequency spectrum, live ▶ run
🎸 Pitch detector — the pitch of a note you hum ▶ run
📈 Linear regression — a line fit to points by gradient descent ▶ run
🏔 Touch the loss landscape — a ball you roll downhill on a loss surface ▶ run
⚔️ adam vs sgd — two optimizers descending the same surface from the same start ▶ run
🌸 Reaction–diffusion — two reacting chemicals that settle into spots and stripes ▶ run
🔬 Lenia — smooth, continuous cellular automata ▶ run
🧲 Spinning magnets — an animated vector field ▶ run

More in the playground: press Ctrl+O for the full gallery.

The language in a minute

Spacing is grouping — hug what belongs together:

1 + 2 * 3,   ; 9 — spaced operators run left-to-right
1 + 2*3      ; 7 — glued operators bind tighter

▶ run

Tensors are the only numbers — a scalar is just a small one, arithmetic broadcasts, and a glued _ indexes:

v: [10, 20, 30],
v + 1,         ; [11, 21, 31]
v_0,           ; 10 — a glued _ indexes
v_(-1),        ; 30 — from the end
v_[2, 0],      ; [30, 10] — with a tensor of indices
Σ(v)           ; 60 — and Σ, μ, sort, fft, conv… are built in

▶ run

Functions and operators are the same thing — anything can be called, anything can sit between its arguments:

1 + 2,                ; 3
+(1, 2),              ; 3 — an operator, called
1 add 2,              ; 3 — a function, infix
1 {x, y | x + y} 2    ; 3 — even a lambda

▶ run

Defining your own operator is just a binding: (++): ListConcat.

Three names for each thing — long for discovery, a word for habit, a glyph for fluency:

TensorSum(0 :: 10),   ; 45
sum(0 :: 10),         ; 45 — same function
Σ(0 :: 10)            ; 45 — same hover card

▶ run

Long names make things findable; the more you use one, the shorter you want it. TensorGradient is grad is — all in scope, all sharing one doc card, so your notation can tighten as you go. And names are unicode throughout: θ, 𝓛, ŷ are fine.

the name tiers at a glance
glyph word full name
grad TensorGradient
Σ sum TensorSum
Π prod TensorProduct
μ mean TensorMean
ceil TensorCeil
floor TensorFloor
max TensorMax
min TensorMin
# length TensorLength
_ gather TensorGather
reshape TensorReshape
:: range TensorRange
outer TensorOuter
^ pow TensorPower
root TensorRoot
% mod TensorRemainder
÷ div TensorDivide
× mul TensorMultiply
~ var TensorVariable
:= TensorAssign
iter FunctionIterate
FunctionPower
. apply FunctionApply
@ eval FunctionEvaluate
$ Reactive
watch TensorWatch
conv TensorConvolution
once SignalOnce

Some cells are still empty — the language is young, and names are earned.

Signals make it live$(…) makes a signal; whatever touches it recomputes when it changes:

x: $(0.5),
(Slider(x), x ^ 2)

▶ run

Training is a few lines~ makes a trainable variable, an optimizer (sgd, adam, adamw, adagrad) minimizes a loss thunk, runs it between frames so the UI stays live:

θ: ~([0, 0]),
𝓛: { Σ((θ - [0.23, 0.47])^2) },
opt: sgd(0.1),
{ opt(𝓛) } ⟳ 100,
θ

▶ run

The full tour lives in the playground — open the built-in Documentation, or hover any built-in for its card.

IDE

Live evaluation on every keystroke, hover docs, unicode completion (type alpha, get α), syntax trees for quoted code, camera and microphone as tensor sources, and LLM code generation — write ;;a bouncing ball;; and it appears (bring your own Anthropic API key, set via the command palette).

Ctrl+O examples · Ctrl+S share as URL · Ctrl+P commands · Ctrl+Space complete — Safari reserves ⌘O, use O there

Run locally

git clone https://github.com/mlajtos/fluent.git
cd fluent && bun install
bun dev   # → http://localhost:3000

The whole thing fits in a handful of files — read it, change it:

file what
language.ts the language — grammar (Ohm), evaluator, prelude; tensors via jax-js, reactivity via preact signals
client.tsx the IDE — components, visualizers, Monaco editor, playground
tests.ts language tests (bun test ./tests.ts)
tests.browser.ts IDE tests in real Chromium (bun run test:browser)

About

tiny lang for differentiable tensors and reactive programming

Topics

Resources

License

Stars

12 stars

Watchers

0 watching

Forks

Contributors

Languages