into fuses a chain at compile time, which is what transducers are for without being them

This commit is contained in:
Joseph Ferano 2026-09-12 22:19:23 +07:00
parent a26469894e
commit 822e74fa06

37
NEXT.md
View File

@ -590,6 +590,43 @@ Two smaller findings, both written down beside the code that ran into them:
Already present and easy to miss: an **EDN parser**, at `vendor/edn/edn.flan`.
## Queued: `into`, fused transformation without transducers
Decided in conversation. **Not transducers, and not Rust's iterators — a macro that fuses the chain at compile time.**
```
(into xs (vec-new i32) (map double) (filter even?))
```
Argument order is **source, destination, then any number of transforms**, matching the `into->` macro the author
already uses in Clojure (`from to xform & xforms`). It reads as a sentence — take this, put it there, doing these —
and the variadic transforms have to trail anyway, which is the mechanical reason they cannot sit in the middle.
Clojure's own `into` composes them into one `xform` first, which is why that macro exists at all.
**Why a macro and not transducers.** Transducers compose at *runtime*: they need function values, closures and
allocation, and every element pays a chain of indirect calls. Rust has no transducers — it has iterators, which are
lazy but fuse into a single loop at compile time via monomorphisation and inlining, needing generics to do it. A
macro reaches the same destination with neither: `(map double xs)` expands to `(double x)` written straight into the
loop body, so the function name is *syntax* and never a value. **No intermediate collection at any step, no closure,
no generics, and nothing to inline.**
**It therefore does not need function values** and is independent of that work.
**What it gives up, and the author does not want it anyway:** you cannot build a transformation at runtime and pass it
around. That is transducers' actual selling point and it is close to useless in a game.
**Why the destination belongs in the form, and why this suits Flan better than `->>` would.** Every collecting
operation here allocates from an *explicit* allocator — that is a frozen rule in `spec-memory.md`. `->>` hides where
the result goes; `into` names it, so the macro knows the destination type, emits the right loop and the right
allocation, and the rule is honoured by construction. plan.org's `->>` threading over slices is the thing this
replaces for the collecting cases.
**Open, and worth settling when it is built:** whether reductions share the form. `(into xs 0 (map cost) (sum))`
reads oddly because zero is not a collection. A second macro with the same shape may be cleaner, so that the
destination is always honest about what it is.
Drop Clojure's `:eduction` branch — that is the pass-around case, and the one part that would need runtime machinery.
## The next batch, in order
Agreed at the end of 2026-09-12. Ordered by priority, not by size. Items 1-3 and 5-6 want the compiler core and should