P Φ
Perspectives & Natural Philosophy

This article was formatted for the web. The original document is available here.

The Declarative Illusion

Ask any infrastructure engineer:

"Is Kubernetes declarative?"

→ They will likely say yes.

"Is SQL declarative?"

→ Yes.

"Is OpenTofu declarative?"

→ Yes.

Then ask:

"Why do you need Helm?"

Or "Why do you use query hints?"

Or "Why does OpenTofu have for_each and count?"

The answers reveal a contradiction. Systems marketed as declarative consistently require imperative escape hatches when applied to complex problems.

A Formal Definition of Declarativity

Let us define declarativity not as a binary property but as a function:

D(S,C)=True iff system S operates declaratively within context C \mathcal{D}(S, C) = \text{True iff system } S \text{ operates declaratively within context } C

Where CC represents a bounded use case — the "canon" within which the system's design matches user needs without requiring imperative logic.

Conversely, let I(S,C)\mathcal{I}(S, C') represent imperative operation when context expands beyond the canon:

D(S,C)=True CC:I(S,C)=True \mathcal{D}(S, C) = \text{True} \implies \exists C' \supset C : \mathcal{I}(S, C') = \text{True}

In English: If a system works declaratively in a bounded context, there exists some expanded context where it will fail to be declarative.

The Recursive Breakdown

When a system encounters context CC' beyond its declarative boundary, the user must employ an imperative escape hatch. This escape hatch, once useful, is often wrapped in a new "declarative" abstraction, creating a recursive tower:

S0imperative escapeS1wrappingS2recursionSn S_0 \xrightarrow{\text{imperative escape}} S_1 \xrightarrow{\text{wrapping}} S_2 \xrightarrow{\text{recursion}} S_n

Each layer Sk+1S_{k+1} presents a declarative interface that internally contains the imperative logic of SkS_k.

Example: The Kubernetes Spiral

Let C0C_0 = "deploy a single static pod". K0K_0 = Kubernetes YAML is declarative for C0C_0.

C0declarativeK0 C_0 \xrightarrow{\text{declarative}} K_0

Now C1C_1 = "deploy the same app across multiple environments". Kubernetes cannot express this declaratively. The escape hatch is Helm — a template engine with conditionals and loops, which is imperative generation.

C1requiresH(imperative generation) C_1 \xrightarrow{\text{requires}} H \quad \text{(imperative generation)}

Let HH be Helm. It works, but now C2C_2 = "deploy multiple Helm releases with ordering". Helm cannot express this declaratively. The escape hatch is Helmfile — an imperative orchestrator.

C2requiresHf(imperative orchestration) C_2 \xrightarrow{\text{requires}} H_f \quad \text{(imperative orchestration)}

This continues. C3C_3 = "custom runtime logic". Kubernetes cannot express this declaratively. The escape hatch is Operators — imperative controllers written in Go.

C3requiresO(imperative controllers) C_3 \xrightarrow{\text{requires}} O \quad \text{(imperative controllers)}

C4C_4 = "manage Kubernetes clusters themselves". Operators cannot express this declaratively. The escape hatch is Terraform/OpenTofu — a declarative wrapper around imperative cluster management.

C4requiresT(declarative wrapper) C_4 \xrightarrow{\text{requires}} T \quad \text{(declarative wrapper)}

The recursion does not terminate:

C0K0C1HC2HfC3OC4T C_0 \rightarrow K_0 \rightarrow C_1 \rightarrow H \rightarrow C_2 \rightarrow H_f \rightarrow C_3 \rightarrow O \rightarrow C_4 \rightarrow T \rightarrow \dots

Each layer presents a declarative interface. Each layer wraps the imperative escape hatch of the layer below. The recursion continues as long as new contexts emerge.

The spiral has no bottom. Only iterative abstraction. \boxed{\text{The spiral has no bottom. Only iterative abstraction.}}

The Easiness Function

Let E(D,C)\mathcal{E}(\mathcal{D}, C) represent the "easiness" of a system — the cognitive load required to use it declaratively within a given context. For the sampled use case C0C_0, easiness is high:

E(D,C0)0 \mathcal{E}(\mathcal{D}, C_0) \gg 0

As the context expands beyond the canon, easiness degrades. The system becomes harder to learn, harder to reason about, and harder to debug.

There exists a decay function mapping context expansion to easiness degradation. Its exact shape requires empirical study; as a first heuristic, we propose the exponential form:

E(x)=E0eλx+δ \mathcal{E}(x) = \mathcal{E}_0 e^{-\lambda x} + \delta

Where:

The exponential form captures a plausible degradation pattern: rapid initial decay as the first escape hatches are encountered, tapering toward a baseline of imperative complexity. However, the exact functional relationship — whether exponential, power law, step function, or other — remains an open question. The central claim is not the specific shape but the existence of decay itself.

Empirical observation: Attempts to extend declarative systems to broader contexts inevitably reduce the easiness advantage that motivated their adoption in the first place. The system becomes harder to use exactly where it needs to be most powerful.

The Purity Test

We can test any system for declarative purity using a simple criterion:

A system is purely declarative for context CC if and only if the user never needs to express:

  1. Ordering (then, before, after)

  2. Iteration (for, while, repeat)

  3. Conditionals (if, case, switch)

  4. State management outside the system's own model

Example

true — the simplest Unix command — is purely declarative when the context remains under "return a successful exit status code of 0". It becomes imperative as soon as the context extends beyond that initial design:

export VAR=true
if [ "$VAR" = "true" ]; then
    echo "Now true is data, not a command"
fi

The symbol true has escaped its declarative canon.

The Recursive Twist

The most subtle aspect of this formalism is the recursive application of declarative procedures:

D(S,C)applyNew context CrequiresI(S,C)wrapSdeclarative?D(S,C) \mathcal{D}(S, C) \xrightarrow{\text{apply}} \text{New context } C' \xrightarrow{\text{requires}} \mathcal{I}(S, C') \xrightarrow{\text{wrap}} S' \xrightarrow{\text{declarative?}} \mathcal{D}(S', C')

Each application of a declarative system to solve a problem expands the effective context. Expanded context reveals edges where the system is not declarative. Those edges become wrapped in new abstractions. The new abstractions claim to be declarative, but they merely hide imperativity one level deeper.

This is not a bug. It is a necessary condition for any system that solves complex problems. The only purely declarative system is one that is never used for anything else than its original bounded context.

Implications

For Tool Builders

No tool is inherently declarative. Design for bounded contexts. Acknowledge escape hatches explicitly. The attempt to make a tool "fully declarative" leads to complexity that erodes its value.

For Tool Users

When evaluating a declarative tool, ask: "What is the sampled use case? Where are the escape hatches? How deep does the recursion go?" The declarative claim is marketing. The escape hatches are engineering.

Conclusion

Declarativity is not a property of a system. It is a relationship between a system and a bounded context. Expand the context, and the declarative illusion shatters. Recursively apply declarative tools, and you build towers of imperative escape hatches wrapped in declarative syntax.

The industry's obsession with "declarative infrastructure" obscures the real engineering: understanding the boundaries of each abstraction, knowing where the imperative escapes live, and accepting that every useful system is imperative at the edges.

The recursive twist is final proof: if you manage to stay declarative as context expands, you lose the easiness that made declarativity desirable in the first place. You cannot have both.

Formal Summary

Let:

Then:

S,C0:D(S,C0)=True \forall \mathcal{S}, \exists \mathcal{C}_0 : \mathcal{D}(\mathcal{S}, \mathcal{C}_0) = \text{True} C1C0:D(S,C1)=False I(S,C1)=True \forall \mathcal{C}_1 \supset \mathcal{C}_0 : \mathcal{D}(\mathcal{S}, \mathcal{C}_1) = \text{False} \implies \mathcal{I}(\mathcal{S}, \mathcal{C}_1) = \text{True} T(I(S,C1))=S2:D(S2,C1)=True \mathcal{T}(\mathcal{I}(\mathcal{S}, \mathcal{C}_1)) = \mathcal{S}_2 : \mathcal{D}(\mathcal{S}_2, \mathcal{C}_1) = \text{True}

This recursion is unbounded:

S0TS1TS2T \mathcal{S}_0 \xrightarrow{\mathcal{T}} \mathcal{S}_1 \xrightarrow{\mathcal{T}} \mathcal{S}_2 \xrightarrow{\mathcal{T}} \ldots

And the easiness function decays:

limnE(DSn,Cn)=Eimperative \lim_{n \to \infty} \mathcal{E}(\mathcal{D}_{\mathcal{S}_n}, \mathcal{C}_n) = \mathcal{E}_{\text{imperative}}

Q.E.D.

< Running On Recursion Emergent Steganography >
© 2026 P. Phi
Content licensed under CC BY 4.0.
Code snippets licensed under MIT License.
Last Update — 30 Apr 2026