Blog

Why We Didn't Choose a Functional Language

What functional languages offer an embedded proxy VM, and how pd-vm covers many of the same needs without making the runtime itself functional


pd-vm is embedded inside a proxy runtime. That changes the decision. The main pressure is not language purity or elegance. It is explicit host effects, compact continuation state, predictable teardown, and straightforward integration with pd-edge.

Functional languages still solve several real problems well. The question is not whether those benefits matter. The question is whether the runtime itself should be built around that model.

What a Functional Language Would Improve

1. Immutability by default

Immutable values reduce aliasing problems and make concurrency easier to reason about.

2. Purity as a semantic boundary

Pure code is easier to cache, replay, and test because its behavior depends only on inputs.

3. Persistent data structures

Structural sharing makes snapshots and forks cheaper when the runtime wants to preserve old state while creating new state.

4. A more uniform high-level model

Closures, expression-oriented control flow, and algebraic-style data handling can make some compiler and language designs feel more coherent.

Why We Did Not Choose It

The runtime is embedded, not standalone

The dominant boundary in this system is the host boundary. Network I/O, transport state, request state, and scheduling all live outside the VM. A functional runtime would not remove that boundary. It would mostly wrap it in a different programming model.

Multiple frontends still need a neutral target

pd-vm serves multiple source languages. Making the runtime itself language-shaped in a functional direction would force every frontend through one more semantic translation step before it could even reach execution.

GC and persistent-runtime assumptions are a poor fit here

Proxy scripts are short-lived, request-scoped, and latency-sensitive. A runtime centered on persistent structures and language-level heap management pushes complexity into exactly the part of the system that needs to stay operationally simple.

Debugging and suspension should stay direct

For this VM, it is useful that execution state is visible as instruction pointer, locals, and stack. That model maps cleanly onto suspension, replay, and debugging in a proxy environment.

How pd-vm and pd-edge Cover the Same Needs

Immutability pressure becomes controlled sharing

Heap-backed values are shared cheaply and become privately owned only when mutation needs ownership. That keeps aliasing manageable without turning the whole runtime into a persistent functional heap.

Purity pressure becomes an explicit host boundary

The meaningful side effects in this system already cross a clear line: host calls. Proxy state, I/O, and protocol transitions are owned by pd-edge, not hidden inside the VM. That makes effectful behavior operationally visible even without a functional effect system.

Snapshot pressure becomes cheap reset and reuse

Instead of relying on persistent language-level data structures, the runtime keeps VM instances cheap to reset and reuse. Shared program artifacts stay loaded, while request-local execution state is rebuilt or reset per request.

Functional-style branching is covered where it matters

The compiler IR already includes optional-value and match-oriented forms. That gives the frontend a direct way to express common proxy logic such as missing metadata, variant handling, and branch-heavy routing without requiring a fully functional runtime model.

Operational simplicity stays in the foreground

The current design keeps suspension, pooling, replay, and debugging aligned with the VM's concrete state model. That is a major advantage in an embedded proxy runtime, where observability and control matter as much as source-language style.

Where This Still Falls Short

Conclusion

We did not reject functional languages because their benefits were irrelevant. We rejected them because an embedded proxy VM has a different center of gravity. The runtime needs clear host boundaries, compact continuation state, GC-free teardown, and low-friction embedding inside pd-edge.

The result is less language-idealized than a functional runtime. It is also a better fit for the operational shape of this system.